Add devices section to own profile modal

Allow for devices to be removed.
This commit is contained in:
JC Brand 2018-08-20 13:40:26 +02:00
parent 8cc69ee5fb
commit 26e936583f
12 changed files with 770 additions and 204 deletions

View File

@ -4371,6 +4371,321 @@
background-color: #e9ecef; background-color: #e9ecef;
border-left: 1px solid #ced4da; border-left: 1px solid #ced4da;
border-radius: 0 0.25rem 0.25rem 0; } border-radius: 0 0.25rem 0.25rem 0; }
#conversejs .nav {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none; }
#conversejs .nav-link {
display: block;
padding: 0.5rem 1rem; }
#conversejs .nav-link:hover, #conversejs .nav-link:focus {
text-decoration: none; }
#conversejs .nav-link.disabled {
color: #6c757d; }
#conversejs .nav-tabs {
border-bottom: 1px solid #dee2e6; }
#conversejs .nav-tabs .nav-item {
margin-bottom: -1px; }
#conversejs .nav-tabs .nav-link {
border: 1px solid transparent;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem; }
#conversejs .nav-tabs .nav-link:hover, #conversejs .nav-tabs .nav-link:focus {
border-color: #e9ecef #e9ecef #dee2e6; }
#conversejs .nav-tabs .nav-link.disabled {
color: #6c757d;
background-color: transparent;
border-color: transparent; }
#conversejs .nav-tabs .nav-link.active,
#conversejs .nav-tabs .nav-item.show .nav-link {
color: #495057;
background-color: #fff;
border-color: #dee2e6 #dee2e6 #fff; }
#conversejs .nav-tabs .dropdown-menu {
margin-top: -1px;
border-top-left-radius: 0;
border-top-right-radius: 0; }
#conversejs .nav-pills .nav-link {
border-radius: 0.25rem; }
#conversejs .nav-pills .nav-link.active,
#conversejs .nav-pills .show > .nav-link {
color: #fff;
background-color: #387592; }
#conversejs .nav-fill .nav-item {
flex: 1 1 auto;
text-align: center; }
#conversejs .nav-justified .nav-item {
flex-basis: 0;
flex-grow: 1;
text-align: center; }
#conversejs .tab-content > .tab-pane {
display: none; }
#conversejs .tab-content > .active {
display: block; }
#conversejs .navbar {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem; }
#conversejs .navbar > .container,
#conversejs .navbar > .container-fluid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between; }
#conversejs .navbar-brand {
display: inline-block;
padding-top: 0.3125rem;
padding-bottom: 0.3125rem;
margin-right: 1rem;
font-size: 1.25rem;
line-height: inherit;
white-space: nowrap; }
#conversejs .navbar-brand:hover, #conversejs .navbar-brand:focus {
text-decoration: none; }
#conversejs .navbar-nav {
display: flex;
flex-direction: column;
padding-left: 0;
margin-bottom: 0;
list-style: none; }
#conversejs .navbar-nav .nav-link {
padding-right: 0;
padding-left: 0; }
#conversejs .navbar-nav .dropdown-menu {
position: static;
float: none; }
#conversejs .navbar-text {
display: inline-block;
padding-top: 0.5rem;
padding-bottom: 0.5rem; }
#conversejs .navbar-collapse {
flex-basis: 100%;
flex-grow: 1;
align-items: center; }
#conversejs .navbar-toggler {
padding: 0.25rem 0.75rem;
font-size: 1.25rem;
line-height: 1;
background-color: transparent;
border: 1px solid transparent;
border-radius: 0.25rem; }
#conversejs .navbar-toggler:hover, #conversejs .navbar-toggler:focus {
text-decoration: none; }
#conversejs .navbar-toggler:not(:disabled):not(.disabled) {
cursor: pointer; }
#conversejs .navbar-toggler-icon {
display: inline-block;
width: 1.5em;
height: 1.5em;
vertical-align: middle;
content: "";
background: no-repeat center center;
background-size: 100% 100%; }
@media (max-width: 575.98px) {
#conversejs .navbar-expand-sm > .container,
#conversejs .navbar-expand-sm > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 576px) {
#conversejs .navbar-expand-sm {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-sm .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-sm .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-sm .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-sm .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-sm > .container,
#conversejs .navbar-expand-sm > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-sm .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-sm .navbar-toggler {
display: none; }
#conversejs .navbar-expand-sm .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
@media (max-width: 767.98px) {
#conversejs .navbar-expand-md > .container,
#conversejs .navbar-expand-md > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 768px) {
#conversejs .navbar-expand-md {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-md .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-md .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-md .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-md .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-md > .container,
#conversejs .navbar-expand-md > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-md .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-md .navbar-toggler {
display: none; }
#conversejs .navbar-expand-md .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
@media (max-width: 991.98px) {
#conversejs .navbar-expand-lg > .container,
#conversejs .navbar-expand-lg > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 992px) {
#conversejs .navbar-expand-lg {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-lg .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-lg .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-lg .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-lg .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-lg > .container,
#conversejs .navbar-expand-lg > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-lg .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-lg .navbar-toggler {
display: none; }
#conversejs .navbar-expand-lg .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
@media (max-width: 1199.98px) {
#conversejs .navbar-expand-xl > .container,
#conversejs .navbar-expand-xl > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 1200px) {
#conversejs .navbar-expand-xl {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-xl .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-xl .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-xl .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-xl .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-xl > .container,
#conversejs .navbar-expand-xl > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-xl .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-xl .navbar-toggler {
display: none; }
#conversejs .navbar-expand-xl .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
#conversejs .navbar-expand {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand > .container,
#conversejs .navbar-expand > .container-fluid {
padding-right: 0;
padding-left: 0; }
#conversejs .navbar-expand .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand > .container,
#conversejs .navbar-expand > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand .navbar-toggler {
display: none; }
#conversejs .navbar-expand .dropup .dropdown-menu {
top: auto;
bottom: 100%; }
#conversejs .navbar-light .navbar-brand {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-brand:hover, #conversejs .navbar-light .navbar-brand:focus {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-nav .nav-link {
color: rgba(0, 0, 0, 0.5); }
#conversejs .navbar-light .navbar-nav .nav-link:hover, #conversejs .navbar-light .navbar-nav .nav-link:focus {
color: rgba(0, 0, 0, 0.7); }
#conversejs .navbar-light .navbar-nav .nav-link.disabled {
color: rgba(0, 0, 0, 0.3); }
#conversejs .navbar-light .navbar-nav .show > .nav-link,
#conversejs .navbar-light .navbar-nav .active > .nav-link,
#conversejs .navbar-light .navbar-nav .nav-link.show,
#conversejs .navbar-light .navbar-nav .nav-link.active {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-toggler {
color: rgba(0, 0, 0, 0.5);
border-color: rgba(0, 0, 0, 0.1); }
#conversejs .navbar-light .navbar-toggler-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }
#conversejs .navbar-light .navbar-text {
color: rgba(0, 0, 0, 0.5); }
#conversejs .navbar-light .navbar-text a {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-text a:hover, #conversejs .navbar-light .navbar-text a:focus {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-dark .navbar-brand {
color: #fff; }
#conversejs .navbar-dark .navbar-brand:hover, #conversejs .navbar-dark .navbar-brand:focus {
color: #fff; }
#conversejs .navbar-dark .navbar-nav .nav-link {
color: rgba(255, 255, 255, 0.5); }
#conversejs .navbar-dark .navbar-nav .nav-link:hover, #conversejs .navbar-dark .navbar-nav .nav-link:focus {
color: rgba(255, 255, 255, 0.75); }
#conversejs .navbar-dark .navbar-nav .nav-link.disabled {
color: rgba(255, 255, 255, 0.25); }
#conversejs .navbar-dark .navbar-nav .show > .nav-link,
#conversejs .navbar-dark .navbar-nav .active > .nav-link,
#conversejs .navbar-dark .navbar-nav .nav-link.show,
#conversejs .navbar-dark .navbar-nav .nav-link.active {
color: #fff; }
#conversejs .navbar-dark .navbar-toggler {
color: rgba(255, 255, 255, 0.5);
border-color: rgba(255, 255, 255, 0.1); }
#conversejs .navbar-dark .navbar-toggler-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }
#conversejs .navbar-dark .navbar-text {
color: rgba(255, 255, 255, 0.5); }
#conversejs .navbar-dark .navbar-text a {
color: #fff; }
#conversejs .navbar-dark .navbar-text a:hover, #conversejs .navbar-dark .navbar-text a:focus {
color: #fff; }
#conversejs .card { #conversejs .card {
position: relative; position: relative;
display: flex; display: flex;
@ -6868,6 +7183,8 @@ body.reset {
font-size: 14px; font-size: 14px;
direction: ltr; direction: ltr;
z-index: 1031; } z-index: 1031; }
#conversejs .nopadding {
padding: 0 !important; }
#conversejs.converse-overlayed > .row { #conversejs.converse-overlayed > .row {
flex-direction: row-reverse; } flex-direction: row-reverse; }
#conversejs.converse-fullscreen .converse-chatboxes, #conversejs.converse-mobile .converse-chatboxes { #conversejs.converse-fullscreen .converse-chatboxes, #conversejs.converse-mobile .converse-chatboxes {
@ -7216,8 +7533,6 @@ body.reset {
#conversejs .btn--small { #conversejs .btn--small {
font-size: 80%; font-size: 80%;
font-weight: normal; } font-weight: normal; }
#conversejs form .form-group {
margin-bottom: 2em; }
#conversejs form .form-check-label { #conversejs form .form-check-label {
margin-top: 0.3rem; } margin-top: 0.3rem; }
#conversejs form .form-control::-webkit-input-placeholder { #conversejs form .form-control::-webkit-input-placeholder {
@ -7289,16 +7604,11 @@ body.reset {
color: #79a5ba; } color: #79a5ba; }
#conversejs form.converse-form .text-muted.error { #conversejs form.converse-form .text-muted.error {
color: #A53214; } color: #A53214; }
#conversejs form.converse-form--modal {
padding-bottom: 0; }
#conversejs form.converse-centered-form { #conversejs form.converse-centered-form {
text-align: center; } text-align: center; }
#conversejs #user-profile-modal label {
font-weight: bold; }
#conversejs .fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%; }
#conversejs .chatbox-navback { #conversejs .chatbox-navback {
display: none; } display: none; }
#conversejs .flyout { #conversejs .flyout {
@ -7857,10 +8167,6 @@ body.reset {
padding: 0.3em 0; padding: 0.3em 0;
clear: left; clear: left;
width: 100%; } width: 100%; }
#conversejs #converse-modals .set-xmpp-status {
margin: 1em; }
#conversejs #converse-modals .set-xmpp-status .custom-control-label {
margin-top: 0.25em; }
#conversejs #controlbox { #conversejs #controlbox {
margin-right: 1.5em; } margin-right: 1.5em; }
#conversejs #controlbox .box-flyout { #conversejs #controlbox .box-flyout {
@ -8261,6 +8567,30 @@ body.reset {
#conversejs.converse-overlayed .converse-chatboxes .chatbox .box-flyout { #conversejs.converse-overlayed .converse-chatboxes .chatbox .box-flyout {
margin-left: 30px; } } margin-left: 30px; } }
#conversejs #converse-modals .set-xmpp-status {
margin: 1em; }
#conversejs #converse-modals .set-xmpp-status .custom-control-label {
margin-top: 0.25em; }
#conversejs #converse-modals #omemo-tabpanel {
margin-top: 1em; }
#conversejs #converse-modals .btn {
font-weight: normal; }
#conversejs #converse-modals #user-profile-modal label {
font-weight: bold; }
#conversejs #converse-modals #user-profile-modal .list-group-item {
display: flex;
justify-content: left;
font-size: 95%; }
#conversejs #converse-modals #user-profile-modal .list-group-item input[type="checkbox"] {
margin-right: 1em; }
#conversejs #converse-modals .fingerprints {
width: 100%;
margin-bottom: 1em; }
#conversejs #converse-modals .fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%; }
#conversejs #converse-roster { #conversejs #converse-roster {
text-align: left; text-align: left;
width: 100%; width: 100%;

330
dist/converse.js vendored
View File

@ -36,19 +36,34 @@
/******/ // define getter function for harmony exports /******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) { /******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) { /******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ } /******/ }
/******/ }; /******/ };
/******/ /******/
/******/ // define __esModule on exports /******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) { /******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ }; /******/ };
/******/ /******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules /******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) { /******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ? /******/ var getter = module && module.__esModule ?
@ -2560,13 +2575,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod
if (_.isFunction(this.beforeRender)) { if (_.isFunction(this.beforeRender)) {
this.beforeRender(); this.beforeRender();
} }
let new_vnode; const new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML()));
if (!_.isNil(this.toHTML)) {
new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML()));
} else {
new_vnode = tovnode.toVNode(this.toDOM());
}
new_vnode.data.hook = _.extend({ new_vnode.data.hook = _.extend({
create: this.updateEventListeners.bind(this), create: this.updateEventListeners.bind(this),
update: this.updateEventListeners.bind(this) update: this.updateEventListeners.bind(this)
@ -27145,13 +27154,12 @@ var map = {
function webpackContext(req) { function webpackContext(req) {
var id = webpackContextResolve(req); var id = webpackContextResolve(req);
var module = __webpack_require__(id); return __webpack_require__(id);
return module;
} }
function webpackContextResolve(req) { function webpackContextResolve(req) {
var id = map[req]; var id = map[req];
if(!(id + 1)) { // check for number or string if(!(id + 1)) { // check for number or string
var e = new Error('Cannot find module "' + req + '".'); var e = new Error("Cannot find module '" + req + "'");
e.code = 'MODULE_NOT_FOUND'; e.code = 'MODULE_NOT_FOUND';
throw e; throw e;
} }
@ -59647,26 +59655,26 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
/*! no static exports found */ /*! no static exports found */
/***/ (function(module, exports) { /***/ (function(module, exports) {
var g; var g;
// This works in non-strict mode // This works in non-strict mode
g = (function() { g = (function() {
return this; return this;
})(); })();
try { try {
// This works if eval is allowed (see CSP) // This works if eval is allowed (see CSP)
g = g || Function("return this")() || (1, eval)("this"); g = g || Function("return this")() || (1, eval)("this");
} catch (e) { } catch (e) {
// This works if the window reference is available // This works if the window reference is available
if (typeof window === "object") g = window; if (typeof window === "object") g = window;
} }
// g can still be undefined, but nothing to do about it... // g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's // We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...} // easier to handle this case. if(!global) { ...}
module.exports = g; module.exports = g;
/***/ }), /***/ }),
@ -59678,28 +59686,28 @@ module.exports = g;
/*! no static exports found */ /*! no static exports found */
/***/ (function(module, exports) { /***/ (function(module, exports) {
module.exports = function(module) { module.exports = function(module) {
if (!module.webpackPolyfill) { if (!module.webpackPolyfill) {
module.deprecate = function() {}; module.deprecate = function() {};
module.paths = []; module.paths = [];
// module.parent = undefined by default // module.parent = undefined by default
if (!module.children) module.children = []; if (!module.children) module.children = [];
Object.defineProperty(module, "loaded", { Object.defineProperty(module, "loaded", {
enumerable: true, enumerable: true,
get: function() { get: function() {
return module.l; return module.l;
} }
}); });
Object.defineProperty(module, "id", { Object.defineProperty(module, "id", {
enumerable: true, enumerable: true,
get: function() { get: function() {
return module.i; return module.i;
} }
}); });
module.webpackPolyfill = 1; module.webpackPolyfill = 1;
} }
return module; return module;
}; };
/***/ }), /***/ }),
@ -74045,6 +74053,43 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
dependencies: ["converse-chatview"], dependencies: ["converse-chatview"],
overrides: { overrides: {
ProfileModal: {
events: {
'change input.select-all': 'selectAll',
'submit .fingerprint-removal': 'removeSelectedFingerprints'
},
initialize() {
const _converse = this.__super__._converse,
device_id = _converse.omemo_store.get('device_id');
this.devicelist = _converse.devicelists.get(_converse.bare_jid);
this.current_device = this.devicelist.devices.get(device_id);
this.other_devices = this.devicelist.devices.filter(d => d.get('id') !== device_id);
this.devicelist.devices.on('change:bundle', this.render, this);
return this.__super__.initialize.apply(this, arguments);
},
selectAll(ev) {
let sibling = ev.target.parentElement.nextElementSibling;
while (sibling) {
sibling.firstElementChild.checked = ev.target.checked;
sibling = sibling.nextElementSibling;
}
},
removeSelectedFingerprints(ev) {
ev.preventDefault();
ev.stopPropagation();
const checkboxes = ev.target.querySelectorAll('.fingerprint-removal-item input[type="checkbox"]:checked'),
device_ids = _.map(checkboxes, 'value');
this.devicelist.removeOwnDevices(device_ids);
}
},
UserDetailsModal: { UserDetailsModal: {
events: { events: {
'click .fingerprint-trust .btn input': 'toggleDeviceTrust' 'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
@ -74376,7 +74421,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
function generateFingerprint(device) { function generateFingerprint(device) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
device.getBundle().then(bundle => { device.getBundle().then(bundle => {
// TODO: only generate fingerprints when necessary if (_.isNil(bundle)) {
resolve();
} // TODO: only generate fingerprints when necessary
crypto.subtle.digest('SHA-1', u.base64ToArrayBuffer(bundle['identity_key'])).then(fp => { crypto.subtle.digest('SHA-1', u.base64ToArrayBuffer(bundle['identity_key'])).then(fp => {
bundle['fingerprint'] = u.arrayBufferToHex(fp); bundle['fingerprint'] = u.arrayBufferToHex(fp);
device.save('bundle', bundle); device.save('bundle', bundle);
@ -74388,10 +74437,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}); });
} }
_converse.getFingerprintsForContact = function (jid) { _converse.generateFingerprints = function (jid) {
return new Promise((resolve, reject) => { return _converse.getDevicesForContact(jid).then(devices => Promise.all(devices.map(d => generateFingerprint(d))));
_converse.getDevicesForContact(jid).then(devices => Promise.all(devices.map(d => generateFingerprint(d))).then(resolve).catch(reject));
});
}; };
_converse.getDevicesForContact = function (jid) { _converse.getDevicesForContact = function (jid) {
@ -74628,24 +74675,23 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}, },
fetchBundleFromServer() { fetchBundleFromServer() {
return new Promise((resolve, reject) => { const stanza = $iq({
const stanza = $iq({ 'type': 'get',
'type': 'get', 'from': _converse.bare_jid,
'from': _converse.bare_jid, 'to': this.get('jid')
'to': this.get('jid') }).c('pubsub', {
}).c('pubsub', { 'xmlns': Strophe.NS.PUBSUB
'xmlns': Strophe.NS.PUBSUB }).c('items', {
}).c('items', { 'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}`
'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}` });
}); return _converse.api.sendIQ(stanza).then(iq => {
const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, iq).pop(),
_converse.connection.sendIQ(stanza, iq => { bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(),
const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, iq).pop(), bundle = parseBundle(bundle_el);
bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(), this.save('bundle', bundle);
bundle = parseBundle(bundle_el); return bundle;
this.save('bundle', bundle); }).catch(iq => {
resolve(bundle); _converse.log(iq.outerHTML, Strophe.LogLevel.ERROR);
}, reject, _converse.IQ_TIMEOUT);
}); });
}, },
@ -74654,7 +74700,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
* this device, if the information is not at hand already. * this device, if the information is not at hand already.
*/ */
if (this.get('bundle')) { if (this.get('bundle')) {
return Promise.resolve(this.get('bundle').toJSON(), this); return Promise.resolve(this.get('bundle'), this);
} else { } else {
return this.fetchBundleFromServer(); return this.fetchBundleFromServer();
} }
@ -74745,6 +74791,9 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
_converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT); _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT);
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
removeOwnDevices(device_ids) {// TODO
} }
}); });
@ -74926,7 +74975,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
_converse.api.listen.on('userDetailsModalInitialized', contact => { _converse.api.listen.on('userDetailsModalInitialized', contact => {
const jid = contact.get('jid'); const jid = contact.get('jid');
_converse.getFingerprintsForContact(jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); _converse.generateFingerprints(jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
});
_converse.api.listen.on('profileModalInitialized', contact => {
_converse.generateFingerprints(_converse.bare_jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}); });
} }
@ -75117,31 +75170,40 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
events: { events: {
'click .change-avatar': "openFileSelection", 'click .change-avatar': "openFileSelection",
'change input[type="file"': "updateFilePreview", 'change input[type="file"': "updateFilePreview",
'submit form': 'onFormSubmitted' 'submit .profile-form': 'onFormSubmitted'
}, },
initialize() { initialize() {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments); _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this); this.model.on('change', this.render, this);
_converse.emit('profileModalInitialized', this.model);
}, },
toHTML() { toHTML() {
return tpl_profile_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { return tpl_profile_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
'_': _,
'__': __,
'_converse': _converse,
'alt_avatar': __('Your avatar image'),
'heading_profile': __('Your Profile'), 'heading_profile': __('Your Profile'),
'label_close': __('Close'), 'label_close': __('Close'),
'label_email': __('Email'), 'label_email': __('Email'),
'label_fullname': __('Full Name'), 'label_fullname': __('Full Name'),
'label_nickname': __('Nickname'),
'label_jid': __('XMPP Address (JID)'), 'label_jid': __('XMPP Address (JID)'),
'label_nickname': __('Nickname'),
'label_role': __('Role'), 'label_role': __('Role'),
'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'), 'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'),
'label_save': __('Save'),
'label_url': __('URL'), 'label_url': __('URL'),
'alt_avatar': __('Your avatar image') 'view': this
})); }));
}, },
afterRender() {
this.tabs = _.map(this.el.querySelectorAll('.nav-item'), tab => new bootstrap.Tab(tab));
},
openFileSelection(ev) { openFileSelection(ev) {
ev.preventDefault(); ev.preventDefault();
this.el.querySelector('input[type="file"]').click(); this.el.querySelector('input[type="file"]').click();
@ -81345,51 +81407,93 @@ __p += '<!-- src/templates/profile_modal.html -->\n<div class="modal fade" id="u
__e(o.heading_profile) + __e(o.heading_profile) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="' + '</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="' +
__e(o.label_close) + __e(o.label_close) +
'"><span aria-hidden="true">&times;</span></button>\n </div>\n <form class="converse-form">\n <div class="modal-body">\n <div class="row">\n <div class="col-auto">\n <a class="change-avatar" href="#">\n '; '"><span aria-hidden="true">&times;</span></button>\n </div>\n <div class="modal-body">\n <ul class="nav nav-pills justify-content-center">\n <li role="presentation" class="nav-item">\n <a class="nav-link active" id="profile-tab" href="#profile-tabpanel" aria-controls="profile-tabpanel" role="tab" data-toggle="tab">Profile</a>\n </li>\n <li role="presentation" class="nav-item">\n <a class="nav-link" id="omemo-tab" href="#omemo-tabpanel" aria-controls="omemo-tabpanel" role="tab" data-toggle="tab">OMEMO</a>\n </li>\n </ul>\n <div class="tab-content">\n <div class="tab-pane fade show active" id="profile-tabpanel" role="tabpanel" aria-labelledby="profile-tab">\n <form class="converse-form converse-form--modal profile-form" action="#">\n <div class="row">\n <div class="col-auto">\n <a class="change-avatar" href="#">\n ';
if (o.image) { ; if (o.image) { ;
__p += '\n <img alt="' + __p += '\n <img alt="' +
__e(o.alt_avatar) + __e(o.alt_avatar) +
'" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:' + '" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:' +
__e(o.image_type) + __e(o.image_type) +
';base64,' + ';base64,' +
__e(o.image) + __e(o.image) +
'"/>\n '; '"/>\n ';
} ; } ;
__p += '\n '; __p += '\n ';
if (!o.image) { ; if (!o.image) { ;
__p += '\n <canvas class="avatar" height="100px" width="100px"/>\n '; __p += '\n <canvas class="avatar" height="100px" width="100px"/>\n ';
} ; } ;
__p += '\n </a>\n <input class="hidden" name="image" type="file">\n </div>\n <div class="col">\n <div class="form-group">\n <label class="col-form-label">' + __p += '\n </a>\n <input class="hidden" name="image" type="file">\n </div>\n <div class="col">\n <div class="form-group">\n <label class="col-form-label">' +
__e(o.label_jid) + __e(o.label_jid) +
':</label>\n <div>' + ':</label>\n <div>' +
__e(o.jid) + __e(o.jid) +
'</div>\n </div>\n </div>\n </div>\n <div class="form-group">\n <label for="vcard-fullname" class="col-form-label">' + '</div>\n </div>\n </div>\n </div>\n <div class="form-group">\n <label for="vcard-fullname" class="col-form-label">' +
__e(o.label_fullname) + __e(o.label_fullname) +
':</label>\n <input id="vcard-fullname" type="text" class="form-control" name="fn" value="' + ':</label>\n <input id="vcard-fullname" type="text" class="form-control" name="fn" value="' +
__e(o.fullname) + __e(o.fullname) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-nickname" class="col-form-label">' + '">\n </div>\n <div class="form-group">\n <label for="vcard-nickname" class="col-form-label">' +
__e(o.label_nickname) + __e(o.label_nickname) +
':</label>\n <input id="vcard-nickname" type="text" class="form-control" name="nickname" value="' + ':</label>\n <input id="vcard-nickname" type="text" class="form-control" name="nickname" value="' +
__e(o.nickname) + __e(o.nickname) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-url" class="col-form-label">' + '">\n </div>\n <div class="form-group">\n <label for="vcard-url" class="col-form-label">' +
__e(o.label_url) + __e(o.label_url) +
':</label>\n <input id="vcard-url" type="url" class="form-control" name="url" value="' + ':</label>\n <input id="vcard-url" type="url" class="form-control" name="url" value="' +
__e(o.url) + __e(o.url) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-email" class="col-form-label">' + '">\n </div>\n <div class="form-group">\n <label for="vcard-email" class="col-form-label">' +
__e(o.label_email) + __e(o.label_email) +
':</label>\n <input id="vcard-email" type="email" class="form-control" name="email" value="' + ':</label>\n <input id="vcard-email" type="email" class="form-control" name="email" value="' +
__e(o.email) + __e(o.email) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-role" class="col-form-label">' + '">\n </div>\n <div class="form-group">\n <label for="vcard-role" class="col-form-label">' +
__e(o.label_role) + __e(o.label_role) +
':</label>\n <input id="vcard-role" type="text" class="form-control" name="role" value="' + ':</label>\n <input id="vcard-role" type="text" class="form-control" name="role" value="' +
__e(o.role) + __e(o.role) +
'" aria-describedby="vcard-role-help">\n <small id="vcard-role-help" class="form-text text-muted">' + '" aria-describedby="vcard-role-help">\n <small id="vcard-role-help" class="form-text text-muted">' +
__e(o.label_role_help) + __e(o.label_role_help) +
'</small>\n </div>\n </div>\n <div class="modal-footer">\n <button type="submit" class="save-form btn btn-primary">' + '</small>\n </div>\n <hr/>\n <div class="form-group">\n <button type="submit" class="save-form btn btn-primary">' +
__e(o.label_save) + __e(o.__('Save and close')) +
'</button>\n <button type="button" class="btn btn-secondary" data-dismiss="modal">' + '</button>\n </div>\n </form>\n </div>\n ';
__e(o.label_close) + if (o._converse.pluggable.plugins['converse-omemo'].enabled()) { ;
'</button>\n </div>\n </form>\n </div>\n </div>\n</div>\n'; __p += '\n <div class="tab-pane fade" id="omemo-tabpanel" role="tabpanel" aria-labelledby="omemo-tab">\n <form class="converse-form fingerprint-removal">\n <ul class="list-group fingerprints">\n <li class="list-group-item active">' +
__e(o.__("This device's OMEMO fingerprint")) +
'</li>\n <li class="fingerprint-removal-item list-group-item">\n ';
if (o.view.current_device.get('bundle') && o.view.current_device.get('bundle').fingerprint) { ;
__p += '\n <input type="checkbox" value="' +
__e(o.view.current_device.get('id')) +
'"\n aria-label="' +
__e(o.__('Checkbox for removing the following fingerprint')) +
'">\n <span class="fingerprint">' +
__e(o.view.current_device.get('bundle').fingerprint) +
'</span>\n ';
} else {;
__p += '\n <span class="spinner fa fa-spinner centered"/>\n ';
} ;
__p += '\n </li>\n </ul>\n ';
if (o.view.other_devices) { ;
__p += '\n <ul class="list-group fingerprints">\n <li class="list-group-item active">\n <input type="checkbox" class="select-all" title="' +
__e(o.__('Select all')) +
'"\n aria-label="' +
__e(o.__('Checkbox to select fingerprints of all other OMEMO devices')) +
'">\n ' +
__e(o.__('Other OMEMO-enabled devices')) +
'\n </li>\n ';
o._.forEach(o.view.other_devices, function (device) { ;
__p += '\n ';
if (device.get('bundle') && device.get('bundle').fingerprint) { ;
__p += '\n <li class="fingerprint-removal-item list-group-item">\n <input type="checkbox" value="' +
__e(device.get('id')) +
'"\n aria-label="' +
__e(o.__('Checkbox for selecting the following fingerprint')) +
'">\n <span class="fingerprint">' +
__e(device.get('bundle').fingerprint) +
'</span>\n </li>\n ';
} ;
__p += '\n ';
}); ;
__p += '\n </ul>\n ';
} ;
__p += '\n <div class="form-group">\n <button type="submit" class="save-form btn btn-primary">' +
__e(o.__('Remove checked devices and close')) +
'</button>\n </div>\n </form>\n </div>\n ';
} ;
__p += '\n </div>\n </div>\n </div>\n </div>\n</div>\n';
return __p return __p
}; };
@ -82407,9 +82511,7 @@ __p += '\n ';
o.view.devicelist.devices.each(function (device) { ; o.view.devicelist.devices.each(function (device) { ;
__p += '\n '; __p += '\n ';
if (device.get('bundle') && device.get('bundle').fingerprint) { ; if (device.get('bundle') && device.get('bundle').fingerprint) { ;
__p += '\n <li class="list-group-item">\n <form class="fingerprint-trust">\n <span class="fingerprint">' + __p += '\n <li class="list-group-item">\n <form class="fingerprint-trust">\n <div class="btn-group btn-group-toggle">\n <label class="btn btn--small ';
__e(device.get('bundle').fingerprint) +
'</span>\n <div class="btn-group btn-group-toggle">\n <label class="btn btn--small ';
if (device.get('trusted') !== -1) { ; if (device.get('trusted') !== -1) { ;
__p += ' btn-primary active '; __p += ' btn-primary active ';
} else { ; } else { ;
@ -82437,7 +82539,9 @@ __p += ' checked="checked" ';
} ; } ;
__p += '>' + __p += '>' +
__e(o.__('Untrusted')) + __e(o.__('Untrusted')) +
'\n </label>\n </div>\n </form>\n </li>\n '; '\n </label>\n </div>\n <span class="fingerprint">' +
__e(device.get('bundle').fingerprint) +
'</span>\n </form>\n </li>\n ';
} ; } ;
__p += '\n '; __p += '\n ';
}); ; }); ;

View File

@ -60,15 +60,6 @@
width: 100%; width: 100%;
} }
#converse-modals {
.set-xmpp-status {
margin: 1em;
.custom-control-label {
margin-top: 0.25em;
}
}
}
#controlbox { #controlbox {
.box-flyout { .box-flyout {
background-color: white; background-color: white;

View File

@ -61,6 +61,10 @@ body.reset {
direction: ltr; direction: ltr;
z-index: 1031; // One more than bootstrap navbar z-index: 1031; // One more than bootstrap navbar
.nopadding {
padding: 0 !important;
}
&.converse-overlayed { &.converse-overlayed {
> .row { > .row {
flex-direction: row-reverse; flex-direction: row-reverse;

View File

@ -5,10 +5,6 @@
} }
form { form {
.form-group {
margin-bottom: 2em;
}
.form-check-label { .form-check-label {
margin-top: $form-check-input-margin-y; margin-top: $form-check-input-margin-y;
} }
@ -108,6 +104,11 @@
} }
} }
} }
&.converse-form--modal {
padding-bottom: 0;
}
&.converse-centered-form { &.converse-centered-form {
text-align: center; text-align: center;
} }

44
sass/_modal.scss Normal file
View File

@ -0,0 +1,44 @@
#conversejs {
#converse-modals {
.set-xmpp-status {
margin: 1em;
.custom-control-label {
margin-top: 0.25em;
}
}
#omemo-tabpanel {
margin-top: 1em;
}
.btn {
font-weight: normal;
}
#user-profile-modal {
label {
font-weight: bold;
}
.list-group-item {
display: flex;
justify-content: left;
font-size: 95%;
input[type="checkbox"] {
margin-right: 1em;
}
}
}
.fingerprints {
width: 100%;
margin-bottom: 1em;
}
.fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%;
}
}
}

View File

@ -1,12 +0,0 @@
#conversejs {
#user-profile-modal {
label {
font-weight: bold;
}
}
.fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%;
}
}

View File

@ -26,6 +26,8 @@
@import "bootstrap/scss/button-group"; @import "bootstrap/scss/button-group";
@import "bootstrap/scss/input-group"; @import "bootstrap/scss/input-group";
@import "bootstrap/scss/custom-forms"; @import "bootstrap/scss/custom-forms";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar";
@import "bootstrap/scss/card"; @import "bootstrap/scss/card";
@import "bootstrap/scss/breadcrumb"; @import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/badge"; @import "bootstrap/scss/badge";
@ -40,9 +42,9 @@
} }
@import "core"; @import "core";
@import "forms"; @import "forms";
@import "profile";
@import "chatbox"; @import "chatbox";
@import "controlbox"; @import "controlbox";
@import "modal";
@import "roster"; @import "roster";
@import "lists"; @import "lists";
@import "chatrooms"; @import "chatrooms";

View File

@ -70,6 +70,41 @@
overrides: { overrides: {
ProfileModal: {
events: {
'change input.select-all': 'selectAll',
'submit .fingerprint-removal': 'removeSelectedFingerprints'
},
initialize () {
const { _converse } = this.__super__,
device_id = _converse.omemo_store.get('device_id');
this.devicelist = _converse.devicelists.get(_converse.bare_jid);
this.current_device = this.devicelist.devices.get(device_id);
this.other_devices = this.devicelist.devices.filter(d => (d.get('id') !== device_id));
this.devicelist.devices.on('change:bundle', this.render, this);
return this.__super__.initialize.apply(this, arguments);
},
selectAll (ev) {
let sibling = ev.target.parentElement.nextElementSibling;
while (sibling) {
sibling.firstElementChild.checked = ev.target.checked;
sibling = sibling.nextElementSibling;
}
},
removeSelectedFingerprints (ev) {
ev.preventDefault();
ev.stopPropagation();
const checkboxes = ev.target.querySelectorAll('.fingerprint-removal-item input[type="checkbox"]:checked'),
device_ids = _.map(checkboxes, 'value');
this.devicelist.removeOwnDevices(device_ids);
},
},
UserDetailsModal: { UserDetailsModal: {
events: { events: {
'click .fingerprint-trust .btn input': 'toggleDeviceTrust' 'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
@ -415,7 +450,7 @@
}); });
} }
_converse.getFingerprintsForContact = function (jid) { _converse.generateFingerprints= function (jid) {
return _converse.getDevicesForContact(jid) return _converse.getDevicesForContact(jid)
.then(devices => Promise.all(devices.map(d => generateFingerprint(d)))) .then(devices => Promise.all(devices.map(d => generateFingerprint(d))))
} }
@ -734,6 +769,10 @@
}); });
_converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT); _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT);
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
removeOwnDevices (device_ids) {
// TODO
} }
}); });
@ -893,7 +932,11 @@
_converse.api.listen.on('userDetailsModalInitialized', (contact) => { _converse.api.listen.on('userDetailsModalInitialized', (contact) => {
const jid = contact.get('jid'); const jid = contact.get('jid');
_converse.getFingerprintsForContact(jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); _converse.generateFingerprints(jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
});
_converse.api.listen.on('profileModalInitialized', (contact) => {
_converse.generateFingerprints(_converse.bare_jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}); });
} }
}); });

View File

@ -48,32 +48,40 @@
events: { events: {
'click .change-avatar': "openFileSelection", 'click .change-avatar': "openFileSelection",
'change input[type="file"': "updateFilePreview", 'change input[type="file"': "updateFilePreview",
'submit form': 'onFormSubmitted' 'submit .profile-form': 'onFormSubmitted'
}, },
initialize () { initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments); _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this); this.model.on('change', this.render, this);
_converse.emit('profileModalInitialized', this.model);
}, },
toHTML () { toHTML () {
return tpl_profile_modal(_.extend( return tpl_profile_modal(_.extend(
this.model.toJSON(), this.model.toJSON(),
this.model.vcard.toJSON(), { this.model.vcard.toJSON(), {
'_': _,
'__': __,
'_converse': _converse,
'alt_avatar': __('Your avatar image'),
'heading_profile': __('Your Profile'), 'heading_profile': __('Your Profile'),
'label_close': __('Close'), 'label_close': __('Close'),
'label_email': __('Email'), 'label_email': __('Email'),
'label_fullname': __('Full Name'), 'label_fullname': __('Full Name'),
'label_nickname': __('Nickname'),
'label_jid': __('XMPP Address (JID)'), 'label_jid': __('XMPP Address (JID)'),
'label_nickname': __('Nickname'),
'label_role': __('Role'), 'label_role': __('Role'),
'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'), 'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'),
'label_save': __('Save'),
'label_url': __('URL'), 'label_url': __('URL'),
'alt_avatar': __('Your avatar image') 'view': this
})); }));
}, },
afterRender () {
this.tabs = _.map(this.el.querySelectorAll('.nav-item'), (tab) => new bootstrap.Tab(tab));
},
openFileSelection (ev) { openFileSelection (ev) {
ev.preventDefault(); ev.preventDefault();
this.el.querySelector('input[type="file"]').click(); this.el.querySelector('input[type="file"]').click();

View File

@ -5,54 +5,105 @@
<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">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">&times;</span></button>
</div> </div>
<form class="converse-form"> <div class="modal-body">
<div class="modal-body"> <ul class="nav nav-pills justify-content-center">
<div class="row"> <li role="presentation" class="nav-item">
<div class="col-auto"> <a class="nav-link active" id="profile-tab" href="#profile-tabpanel" aria-controls="profile-tabpanel" role="tab" data-toggle="tab">Profile</a>
<a class="change-avatar" href="#"> </li>
{[ if (o.image) { ]} <li role="presentation" class="nav-item">
<img alt="{{{o.alt_avatar}}}" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/> <a class="nav-link" id="omemo-tab" href="#omemo-tabpanel" aria-controls="omemo-tabpanel" role="tab" data-toggle="tab">OMEMO</a>
{[ } ]} </li>
{[ if (!o.image) { ]} </ul>
<canvas class="avatar" height="100px" width="100px"/> <div class="tab-content">
{[ } ]} <div class="tab-pane fade show active" id="profile-tabpanel" role="tabpanel" aria-labelledby="profile-tab">
</a> <form class="converse-form converse-form--modal profile-form" action="#">
<input class="hidden" name="image" type="file"> <div class="row">
</div> <div class="col-auto">
<div class="col"> <a class="change-avatar" href="#">
<div class="form-group"> {[ if (o.image) { ]}
<label class="col-form-label">{{{o.label_jid}}}:</label> <img alt="{{{o.alt_avatar}}}" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
<div>{{{o.jid}}}</div> {[ } ]}
{[ 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="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}}}" aria-describedby="vcard-role-help">
<small id="vcard-role-help" class="form-text text-muted">{{{o.label_role_help}}}</small>
</div>
<hr/>
<div class="form-group">
<button type="submit" class="save-form btn btn-primary">{{{o.__('Save and close')}}}</button>
</div>
</form>
</div>
{[ if (o._converse.pluggable.plugins['converse-omemo'].enabled()) { ]}
<div class="tab-pane fade" id="omemo-tabpanel" role="tabpanel" aria-labelledby="omemo-tab">
<form class="converse-form fingerprint-removal">
<ul class="list-group fingerprints">
<li class="list-group-item active">{{{o.__("This device's OMEMO fingerprint")}}}</li>
<li class="fingerprint-removal-item list-group-item">
{[ if (o.view.current_device.get('bundle') && o.view.current_device.get('bundle').fingerprint) { ]}
<input type="checkbox" value="{{{o.view.current_device.get('id')}}}"
aria-label="{{{o.__('Checkbox for removing the following fingerprint')}}}">
<span class="fingerprint">{{{o.view.current_device.get('bundle').fingerprint}}}</span>
{[ } else {]}
<span class="spinner fa fa-spinner centered"/>
{[ } ]}
</li>
</ul>
{[ if (o.view.other_devices) { ]}
<ul class="list-group fingerprints">
<li class="list-group-item active">
<input type="checkbox" class="select-all" title="{{{o.__('Select all')}}}"
aria-label="{{{o.__('Checkbox to select fingerprints of all other OMEMO devices')}}}">
{{{o.__('Other OMEMO-enabled devices')}}}
</li>
{[ o._.forEach(o.view.other_devices, function (device) { ]}
{[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]}
<li class="fingerprint-removal-item list-group-item">
<input type="checkbox" value="{{{device.get('id')}}}"
aria-label="{{{o.__('Checkbox for selecting the following fingerprint')}}}">
<span class="fingerprint">{{{device.get('bundle').fingerprint}}}</span>
</li>
{[ } ]}
{[ }); ]}
</ul>
{[ } ]}
<div class="form-group">
<button type="submit" class="save-form btn btn-primary">{{{o.__('Remove checked devices and close')}}}</button>
</div>
</form>
</div> </div>
</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}}}" aria-describedby="vcard-role-help">
<small id="vcard-role-help" class="form-text text-muted">{{{o.label_role_help}}}</small>
</div>
</div> </div>
<div class="modal-footer"> </div>
<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>

View File

@ -40,7 +40,6 @@
{[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]} {[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]}
<li class="list-group-item"> <li class="list-group-item">
<form class="fingerprint-trust"> <form class="fingerprint-trust">
<span class="fingerprint">{{{device.get('bundle').fingerprint}}}</span>
<div class="btn-group btn-group-toggle"> <div class="btn-group btn-group-toggle">
<label class="btn btn--small {[ if (device.get('trusted') !== -1) { ]} btn-primary active {[ } else { ]} btn-secondary {[ } ]}"> <label class="btn btn--small {[ if (device.get('trusted') !== -1) { ]} btn-primary active {[ } else { ]} btn-secondary {[ } ]}">
<input type="radio" name="{{{device.get('id')}}}" value="1" <input type="radio" name="{{{device.get('id')}}}" value="1"
@ -51,6 +50,7 @@
{[ if (device.get('trusted') === -1) { ]} checked="checked" {[ } ]}>{{{o.__('Untrusted')}}} {[ if (device.get('trusted') === -1) { ]} checked="checked" {[ } ]}>{{{o.__('Untrusted')}}}
</label> </label>
</div> </div>
<span class="fingerprint">{{{device.get('bundle').fingerprint}}}</span>
</form> </form>
</li> </li>
{[ } ]} {[ } ]}