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;
border-left: 1px solid #ced4da;
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 {
position: relative;
display: flex;
@ -6868,6 +7183,8 @@ body.reset {
font-size: 14px;
direction: ltr;
z-index: 1031; }
#conversejs .nopadding {
padding: 0 !important; }
#conversejs.converse-overlayed > .row {
flex-direction: row-reverse; }
#conversejs.converse-fullscreen .converse-chatboxes, #conversejs.converse-mobile .converse-chatboxes {
@ -7216,8 +7533,6 @@ body.reset {
#conversejs .btn--small {
font-size: 80%;
font-weight: normal; }
#conversejs form .form-group {
margin-bottom: 2em; }
#conversejs form .form-check-label {
margin-top: 0.3rem; }
#conversejs form .form-control::-webkit-input-placeholder {
@ -7289,16 +7604,11 @@ body.reset {
color: #79a5ba; }
#conversejs form.converse-form .text-muted.error {
color: #A53214; }
#conversejs form.converse-form--modal {
padding-bottom: 0; }
#conversejs form.converse-centered-form {
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 {
display: none; }
#conversejs .flyout {
@ -7857,10 +8167,6 @@ body.reset {
padding: 0.3em 0;
clear: left;
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 {
margin-right: 1.5em; }
#conversejs #controlbox .box-flyout {
@ -8261,6 +8567,30 @@ body.reset {
#conversejs.converse-overlayed .converse-chatboxes .chatbox .box-flyout {
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 {
text-align: left;
width: 100%;

330
dist/converse.js vendored
View File

@ -36,19 +36,34 @@
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on 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 });
/******/ };
/******/
/******/ // 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
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
@ -2560,13 +2575,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod
if (_.isFunction(this.beforeRender)) {
this.beforeRender();
}
let new_vnode;
if (!_.isNil(this.toHTML)) {
new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML()));
} else {
new_vnode = tovnode.toVNode(this.toDOM());
}
const new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML()));
new_vnode.data.hook = _.extend({
create: this.updateEventListeners.bind(this),
update: this.updateEventListeners.bind(this)
@ -27145,13 +27154,12 @@ var map = {
function webpackContext(req) {
var id = webpackContextResolve(req);
var module = __webpack_require__(id);
return module;
return __webpack_require__(id);
}
function webpackContextResolve(req) {
var id = map[req];
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';
throw e;
}
@ -59647,26 +59655,26 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
/*! no static exports found */
/***/ (function(module, exports) {
var g;
// This works in non-strict mode
g = (function() {
return this;
})();
try {
// This works if eval is allowed (see CSP)
g = g || Function("return this")() || (1, eval)("this");
} catch (e) {
// This works if the window reference is available
if (typeof window === "object") g = window;
}
// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}
module.exports = g;
var g;
// This works in non-strict mode
g = (function() {
return this;
})();
try {
// This works if eval is allowed (see CSP)
g = g || Function("return this")() || (1, eval)("this");
} catch (e) {
// This works if the window reference is available
if (typeof window === "object") g = window;
}
// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}
module.exports = g;
/***/ }),
@ -59678,28 +59686,28 @@ module.exports = g;
/*! no static exports found */
/***/ (function(module, exports) {
module.exports = function(module) {
if (!module.webpackPolyfill) {
module.deprecate = function() {};
module.paths = [];
// module.parent = undefined by default
if (!module.children) module.children = [];
Object.defineProperty(module, "loaded", {
enumerable: true,
get: function() {
return module.l;
}
});
Object.defineProperty(module, "id", {
enumerable: true,
get: function() {
return module.i;
}
});
module.webpackPolyfill = 1;
}
return module;
};
module.exports = function(module) {
if (!module.webpackPolyfill) {
module.deprecate = function() {};
module.paths = [];
// module.parent = undefined by default
if (!module.children) module.children = [];
Object.defineProperty(module, "loaded", {
enumerable: true,
get: function() {
return module.l;
}
});
Object.defineProperty(module, "id", {
enumerable: true,
get: function() {
return module.i;
}
});
module.webpackPolyfill = 1;
}
return module;
};
/***/ }),
@ -74045,6 +74053,43 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
dependencies: ["converse-chatview"],
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: {
events: {
'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
@ -74376,7 +74421,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
function generateFingerprint(device) {
return new Promise((resolve, reject) => {
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 => {
bundle['fingerprint'] = u.arrayBufferToHex(fp);
device.save('bundle', bundle);
@ -74388,10 +74437,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
});
}
_converse.getFingerprintsForContact = function (jid) {
return new Promise((resolve, reject) => {
_converse.getDevicesForContact(jid).then(devices => Promise.all(devices.map(d => generateFingerprint(d))).then(resolve).catch(reject));
});
_converse.generateFingerprints = function (jid) {
return _converse.getDevicesForContact(jid).then(devices => Promise.all(devices.map(d => generateFingerprint(d))));
};
_converse.getDevicesForContact = function (jid) {
@ -74628,24 +74675,23 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
},
fetchBundleFromServer() {
return new Promise((resolve, reject) => {
const stanza = $iq({
'type': 'get',
'from': _converse.bare_jid,
'to': this.get('jid')
}).c('pubsub', {
'xmlns': Strophe.NS.PUBSUB
}).c('items', {
'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}`
});
_converse.connection.sendIQ(stanza, iq => {
const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, iq).pop(),
bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(),
bundle = parseBundle(bundle_el);
this.save('bundle', bundle);
resolve(bundle);
}, reject, _converse.IQ_TIMEOUT);
const stanza = $iq({
'type': 'get',
'from': _converse.bare_jid,
'to': this.get('jid')
}).c('pubsub', {
'xmlns': Strophe.NS.PUBSUB
}).c('items', {
'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(),
bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(),
bundle = parseBundle(bundle_el);
this.save('bundle', bundle);
return bundle;
}).catch(iq => {
_converse.log(iq.outerHTML, Strophe.LogLevel.ERROR);
});
},
@ -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.
*/
if (this.get('bundle')) {
return Promise.resolve(this.get('bundle').toJSON(), this);
return Promise.resolve(this.get('bundle'), this);
} else {
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);
}).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 => {
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: {
'click .change-avatar': "openFileSelection",
'change input[type="file"': "updateFilePreview",
'submit form': 'onFormSubmitted'
'submit .profile-form': 'onFormSubmitted'
},
initialize() {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this);
_converse.emit('profileModalInitialized', this.model);
},
toHTML() {
return tpl_profile_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
'_': _,
'__': __,
'_converse': _converse,
'alt_avatar': __('Your avatar image'),
'heading_profile': __('Your Profile'),
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_nickname': __('Nickname'),
'label_jid': __('XMPP Address (JID)'),
'label_nickname': __('Nickname'),
'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_save': __('Save'),
'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) {
ev.preventDefault();
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) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="' +
__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) { ;
__p += '\n <img alt="' +
__p += '\n <img alt="' +
__e(o.alt_avatar) +
'" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:' +
__e(o.image_type) +
';base64,' +
__e(o.image) +
'"/>\n ';
'"/>\n ';
} ;
__p += '\n ';
__p += '\n ';
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) +
':</label>\n <div>' +
':</label>\n <div>' +
__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) +
':</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) +
'">\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) +
':</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) +
'">\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) +
':</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) +
'">\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) +
':</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) +
'">\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) +
':</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) +
'" 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) +
'</small>\n </div>\n </div>\n <div class="modal-footer">\n <button type="submit" class="save-form btn btn-primary">' +
__e(o.label_save) +
'</button>\n <button type="button" class="btn btn-secondary" data-dismiss="modal">' +
__e(o.label_close) +
'</button>\n </div>\n </form>\n </div>\n </div>\n</div>\n';
'</small>\n </div>\n <hr/>\n <div class="form-group">\n <button type="submit" class="save-form btn btn-primary">' +
__e(o.__('Save and close')) +
'</button>\n </div>\n </form>\n </div>\n ';
if (o._converse.pluggable.plugins['converse-omemo'].enabled()) { ;
__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
};
@ -82407,9 +82511,7 @@ __p += '\n ';
o.view.devicelist.devices.each(function (device) { ;
__p += '\n ';
if (device.get('bundle') && device.get('bundle').fingerprint) { ;
__p += '\n <li class="list-group-item">\n <form class="fingerprint-trust">\n <span class="fingerprint">' +
__e(device.get('bundle').fingerprint) +
'</span>\n <div class="btn-group btn-group-toggle">\n <label class="btn btn--small ';
__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 ';
if (device.get('trusted') !== -1) { ;
__p += ' btn-primary active ';
} else { ;
@ -82437,7 +82539,9 @@ __p += ' checked="checked" ';
} ;
__p += '>' +
__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 ';
}); ;

View File

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

View File

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

View File

@ -5,10 +5,6 @@
}
form {
.form-group {
margin-bottom: 2em;
}
.form-check-label {
margin-top: $form-check-input-margin-y;
}
@ -108,6 +104,11 @@
}
}
}
&.converse-form--modal {
padding-bottom: 0;
}
&.converse-centered-form {
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/input-group";
@import "bootstrap/scss/custom-forms";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar";
@import "bootstrap/scss/card";
@import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/badge";
@ -40,9 +42,9 @@
}
@import "core";
@import "forms";
@import "profile";
@import "chatbox";
@import "controlbox";
@import "modal";
@import "roster";
@import "lists";
@import "chatrooms";

View File

@ -70,6 +70,41 @@
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: {
events: {
'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
@ -415,7 +450,7 @@
});
}
_converse.getFingerprintsForContact = function (jid) {
_converse.generateFingerprints= function (jid) {
return _converse.getDevicesForContact(jid)
.then(devices => Promise.all(devices.map(d => generateFingerprint(d))))
}
@ -734,6 +769,10 @@
});
_converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT);
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
removeOwnDevices (device_ids) {
// TODO
}
});
@ -893,7 +932,11 @@
_converse.api.listen.on('userDetailsModalInitialized', (contact) => {
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: {
'click .change-avatar': "openFileSelection",
'change input[type="file"': "updateFilePreview",
'submit form': 'onFormSubmitted'
'submit .profile-form': 'onFormSubmitted'
},
initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this);
_converse.emit('profileModalInitialized', this.model);
},
toHTML () {
return tpl_profile_modal(_.extend(
this.model.toJSON(),
this.model.vcard.toJSON(), {
'_': _,
'__': __,
'_converse': _converse,
'alt_avatar': __('Your avatar image'),
'heading_profile': __('Your Profile'),
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_nickname': __('Nickname'),
'label_jid': __('XMPP Address (JID)'),
'label_nickname': __('Nickname'),
'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_save': __('Save'),
'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) {
ev.preventDefault();
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>
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">&times;</span></button>
</div>
<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 class="modal-body">
<ul class="nav nav-pills justify-content-center">
<li role="presentation" class="nav-item">
<a class="nav-link active" id="profile-tab" href="#profile-tabpanel" aria-controls="profile-tabpanel" role="tab" data-toggle="tab">Profile</a>
</li>
<li role="presentation" class="nav-item">
<a class="nav-link" id="omemo-tab" href="#omemo-tabpanel" aria-controls="omemo-tabpanel" role="tab" data-toggle="tab">OMEMO</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="profile-tabpanel" role="tabpanel" aria-labelledby="profile-tab">
<form class="converse-form converse-form--modal profile-form" action="#">
<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="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 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 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>

View File

@ -40,7 +40,6 @@
{[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]}
<li class="list-group-item">
<form class="fingerprint-trust">
<span class="fingerprint">{{{device.get('bundle').fingerprint}}}</span>
<div class="btn-group btn-group-toggle">
<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"
@ -51,6 +50,7 @@
{[ if (device.get('trusted') === -1) { ]} checked="checked" {[ } ]}>{{{o.__('Untrusted')}}}
</label>
</div>
<span class="fingerprint">{{{device.get('bundle').fingerprint}}}</span>
</form>
</li>
{[ } ]}