Move modals and their templates into ./modals/
This commit is contained in:
parent
34cba68432
commit
b18cc6bcc5
17
package-lock.json
generated
17
package-lock.json
generated
@ -3144,7 +3144,8 @@
|
||||
"dependencies": {
|
||||
"filesize": {
|
||||
"version": "6.1.0",
|
||||
"resolved": false
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
|
||||
"integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg=="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
@ -3200,20 +3201,22 @@
|
||||
},
|
||||
"localforage": {
|
||||
"version": "1.7.3",
|
||||
"resolved": false,
|
||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz",
|
||||
"integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==",
|
||||
"requires": {
|
||||
"lie": "3.1.1"
|
||||
}
|
||||
},
|
||||
"pluggable.js": {
|
||||
"version": "2.0.1",
|
||||
"resolved": false,
|
||||
"resolved": "https://registry.npmjs.org/pluggable.js/-/pluggable.js-2.0.1.tgz",
|
||||
"integrity": "sha512-SBt6v6Tbp20Jf8hU0cpcc/+HBHGMY8/Q+yA6Ih0tBQE8tfdZ6U4PRG0iNvUUjLx/hVyOP53n0UfGBymlfaaXCg==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.11"
|
||||
}
|
||||
},
|
||||
"skeletor.js": {
|
||||
"version": "0.0.1",
|
||||
"version": "github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b561",
|
||||
"from": "github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b561",
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
@ -3221,7 +3224,11 @@
|
||||
},
|
||||
"strophe.js": {
|
||||
"version": "github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f",
|
||||
"from": "strophe.js@github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f"
|
||||
"from": "strophe.js@github:strophe/strophejs#c4a94e59877c06dc2395f4ccbd26f3fee67a4c9f",
|
||||
"requires": {
|
||||
"abab": "^2.0.3",
|
||||
"xmldom": "^0.1.27"
|
||||
}
|
||||
},
|
||||
"twemoji": {
|
||||
"version": "12.1.5",
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*global mock */
|
||||
/*global mock, converse */
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
@ -53,6 +53,7 @@ describe("The User Details Modal", function () {
|
||||
let remove_contact_button = modal.el.querySelector('button.remove-contact');
|
||||
expect(u.isVisible(remove_contact_button)).toBeTruthy();
|
||||
remove_contact_button.click();
|
||||
|
||||
await u.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
|
||||
|
||||
const header = document.querySelector('.alert-danger .modal-title');
|
||||
|
@ -3,277 +3,133 @@
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import { View } from '@converse/skeletor/src/view.js';
|
||||
import Alert from './modals/alert.js';
|
||||
import BootstrapModal from './modals/base.js';
|
||||
import Confirm from './modals/confirm.js';
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { render } from 'lit-html';
|
||||
import { __ } from './i18n';
|
||||
import bootstrap from "bootstrap.native";
|
||||
import { converse } from "@converse/headless/converse-core";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_alert_component from "templates/alert.js";
|
||||
import tpl_alert_modal from "templates/alert_modal.js";
|
||||
import tpl_prompt from "templates/prompt.js";
|
||||
import { _converse, converse } from "@converse/headless/converse-core";
|
||||
|
||||
const { sizzle } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
let _converse;
|
||||
|
||||
|
||||
export const BootstrapModal = View.extend({
|
||||
className: "modal",
|
||||
events: {
|
||||
'click .nav-item .nav-link': 'switchTab'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.render()
|
||||
|
||||
this.el.setAttribute('tabindex', '-1');
|
||||
this.el.setAttribute('role', 'dialog');
|
||||
this.el.setAttribute('aria-hidden', 'true');
|
||||
const label_id = this.el.querySelector('.modal-title').getAttribute('id');
|
||||
label_id && this.el.setAttribute('aria-labelledby', label_id);
|
||||
|
||||
this.insertIntoDOM();
|
||||
const Modal = bootstrap.Modal;
|
||||
this.modal = new Modal(this.el, {
|
||||
backdrop: true,
|
||||
keyboard: true
|
||||
});
|
||||
this.el.addEventListener('hide.bs.modal', () => u.removeClass('selected', this.trigger_el), false);
|
||||
},
|
||||
|
||||
insertIntoDOM () {
|
||||
const container_el = _converse.chatboxviews.el.querySelector("#converse-modals");
|
||||
container_el.insertAdjacentElement('beforeEnd', this.el);
|
||||
},
|
||||
|
||||
switchTab (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
sizzle('.nav-link.active', this.el).forEach(el => {
|
||||
u.removeClass('active', this.el.querySelector(el.getAttribute('href')));
|
||||
u.removeClass('active', el);
|
||||
});
|
||||
u.addClass('active', ev.target);
|
||||
u.addClass('active', this.el.querySelector(ev.target.getAttribute('href')))
|
||||
},
|
||||
|
||||
alert (message, type='primary') {
|
||||
const body = this.el.querySelector('.modal-alert');
|
||||
if (body === null) {
|
||||
log.error("Could not find a .modal-alert element in the modal to show an alert message in!");
|
||||
return;
|
||||
}
|
||||
// FIXME: Instead of adding the alert imperatively, we should
|
||||
// find a way to let the modal rerender with an alert message
|
||||
render(tpl_alert_component({'type': `alert-${type}`, 'message': message}), body);
|
||||
const el = body.firstElementChild;
|
||||
setTimeout(() => {
|
||||
u.addClass('fade-out', el);
|
||||
setTimeout(() => u.removeElement(el), 600);
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
show (ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
this.trigger_el = ev.target;
|
||||
this.trigger_el.classList.add('selected');
|
||||
}
|
||||
this.modal.show();
|
||||
}
|
||||
});
|
||||
|
||||
converse.env.BootstrapModal = BootstrapModal; // expose to plugins
|
||||
|
||||
export const Confirm = BootstrapModal.extend({
|
||||
events: {
|
||||
'submit .confirm': 'onConfimation'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.confirmation = u.getResolveablePromise();
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render)
|
||||
this.el.addEventListener('closed.bs.modal', () => this.confirmation.reject(), false);
|
||||
},
|
||||
let alert;
|
||||
|
||||
toHTML () {
|
||||
return tpl_prompt(this.model.toJSON());
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
if (!this.close_handler_registered) {
|
||||
this.el.addEventListener('closed.bs.modal', () => {
|
||||
if (!this.confirmation.isResolved) {
|
||||
this.confirmation.reject()
|
||||
}
|
||||
}, false);
|
||||
this.close_handler_registered = true;
|
||||
const modal_api = {
|
||||
/**
|
||||
* Show a confirm modal to the user.
|
||||
* @method _converse.api.confirm
|
||||
* @param { String } title - The header text for the confirmation dialog
|
||||
* @param { (String[]|String) } messages - The text to show to the user
|
||||
* @param { Array<Field> } fields - An object representing a fields presented to the user.
|
||||
* @property { String } Field.label - The form label for the input field.
|
||||
* @property { String } Field.name - The name for the input field.
|
||||
* @property { String } [Field.challenge] - A challenge value that must be provided by the user.
|
||||
* @property { String } [Field.placeholder] - The placeholder for the input field.
|
||||
* @property { Boolean} [Field.required] - Whether the field is required or not
|
||||
* @returns { Promise<Array|false> } A promise which resolves with an array of
|
||||
* filled in fields or `false` if the confirm dialog was closed or canceled.
|
||||
*/
|
||||
async confirm (title, messages=[], fields=[]) {
|
||||
if (typeof messages === 'string') {
|
||||
messages = [messages];
|
||||
}
|
||||
const model = new Model({title, messages, fields, 'type': 'confirm'})
|
||||
const confirm = new Confirm({model});
|
||||
confirm.show();
|
||||
let result;
|
||||
try {
|
||||
result = await confirm.confirmation;
|
||||
} catch (e) {
|
||||
result = false;
|
||||
}
|
||||
confirm.remove();
|
||||
return result;
|
||||
},
|
||||
|
||||
onConfimation (ev) {
|
||||
ev.preventDefault();
|
||||
const form_data = new FormData(ev.target);
|
||||
const fields = (this.model.get('fields') || [])
|
||||
.map(field => {
|
||||
const value = form_data.get(field.name).trim();
|
||||
field.value = value;
|
||||
if (field.challenge) {
|
||||
field.challenge_failed = (value !== field.challenge);
|
||||
}
|
||||
return field;
|
||||
/**
|
||||
* Show a prompt modal to the user.
|
||||
* @method _converse.api.prompt
|
||||
* @param { String } title - The header text for the prompt
|
||||
* @param { (String[]|String) } messages - The prompt text to show to the user
|
||||
* @param { String } placeholder - The placeholder text for the prompt input
|
||||
* @returns { Promise<String|false> } A promise which resolves with the text provided by the
|
||||
* user or `false` if the user canceled the prompt.
|
||||
*/
|
||||
async prompt (title, messages=[], placeholder='') {
|
||||
if (typeof messages === 'string') {
|
||||
messages = [messages];
|
||||
}
|
||||
const model = new Model({
|
||||
title,
|
||||
messages,
|
||||
'fields': [{
|
||||
'name': 'reason',
|
||||
'placeholder': placeholder,
|
||||
}],
|
||||
'type': 'prompt'
|
||||
})
|
||||
const prompt = new Confirm({model});
|
||||
prompt.show();
|
||||
let result;
|
||||
try {
|
||||
result = (await prompt.confirmation).pop()?.value;
|
||||
} catch (e) {
|
||||
result = false;
|
||||
}
|
||||
prompt.remove();
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show an alert modal to the user.
|
||||
* @method _converse.api.alert
|
||||
* @param { ('info'|'warn'|'error') } type - The type of alert.
|
||||
* @param { String } title - The header text for the alert.
|
||||
* @param { (String[]|String) } messages - The alert text to show to the user.
|
||||
*/
|
||||
alert (type, title, messages) {
|
||||
if (typeof messages === 'string') {
|
||||
messages = [messages];
|
||||
}
|
||||
let level;
|
||||
if (type === 'error') {
|
||||
level = 'alert-danger';
|
||||
} else if (type === 'info') {
|
||||
level = 'alert-info';
|
||||
} else if (type === 'warn') {
|
||||
level = 'alert-warning';
|
||||
}
|
||||
|
||||
if (alert === undefined) {
|
||||
const model = new Model({
|
||||
'title': title,
|
||||
'messages': messages,
|
||||
'level': level,
|
||||
'type': 'alert'
|
||||
})
|
||||
alert = new Alert({model});
|
||||
} else {
|
||||
alert.model.set({
|
||||
'title': title,
|
||||
'messages': messages,
|
||||
'level': level
|
||||
});
|
||||
|
||||
if (fields.filter(c => c.challenge_failed).length) {
|
||||
this.model.set('fields', fields);
|
||||
// Setting an array doesn't trigger a change event
|
||||
this.model.trigger('change');
|
||||
return;
|
||||
}
|
||||
this.confirmation.resolve(fields);
|
||||
this.modal.hide();
|
||||
alert.show();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export const Alert = BootstrapModal.extend({
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render)
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_alert_modal(Object.assign({__}, this.model.toJSON()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
converse.plugins.add('converse-modal', {
|
||||
|
||||
initialize () {
|
||||
_converse = this._converse
|
||||
|
||||
/************************ BEGIN Event Listeners ************************/
|
||||
_converse.api.listen.on('disconnect', () => {
|
||||
const container = document.querySelector("#converse-modals");
|
||||
if (container) {
|
||||
container.innerHTML = '';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/************************ BEGIN API ************************/
|
||||
// We extend the default converse.js API to add methods specific to MUC chat rooms.
|
||||
let alert;
|
||||
|
||||
Object.assign(_converse.api, {
|
||||
/**
|
||||
* Show a confirm modal to the user.
|
||||
* @method _converse.api.confirm
|
||||
* @param { String } title - The header text for the confirmation dialog
|
||||
* @param { (String[]|String) } messages - The text to show to the user
|
||||
* @param { Array<Field> } fields - An object representing a fields presented to the user.
|
||||
* @property { String } Field.label - The form label for the input field.
|
||||
* @property { String } Field.name - The name for the input field.
|
||||
* @property { String } [Field.challenge] - A challenge value that must be provided by the user.
|
||||
* @property { String } [Field.placeholder] - The placeholder for the input field.
|
||||
* @property { Boolean} [Field.required] - Whether the field is required or not
|
||||
* @returns { Promise<Array|false> } A promise which resolves with an array of
|
||||
* filled in fields or `false` if the confirm dialog was closed or canceled.
|
||||
*/
|
||||
async confirm (title, messages=[], fields=[]) {
|
||||
if (typeof messages === 'string') {
|
||||
messages = [messages];
|
||||
}
|
||||
const model = new Model({title, messages, fields, 'type': 'confirm'})
|
||||
const confirm = new Confirm({model});
|
||||
confirm.show();
|
||||
let result;
|
||||
try {
|
||||
result = await confirm.confirmation;
|
||||
} catch (e) {
|
||||
result = false;
|
||||
}
|
||||
confirm.remove();
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a prompt modal to the user.
|
||||
* @method _converse.api.prompt
|
||||
* @param { String } title - The header text for the prompt
|
||||
* @param { (String[]|String) } messages - The prompt text to show to the user
|
||||
* @param { String } placeholder - The placeholder text for the prompt input
|
||||
* @returns { Promise<String|false> } A promise which resolves with the text provided by the
|
||||
* user or `false` if the user canceled the prompt.
|
||||
*/
|
||||
async prompt (title, messages=[], placeholder='') {
|
||||
if (typeof messages === 'string') {
|
||||
messages = [messages];
|
||||
}
|
||||
const model = new Model({
|
||||
title,
|
||||
messages,
|
||||
'fields': [{
|
||||
'name': 'reason',
|
||||
'placeholder': placeholder,
|
||||
}],
|
||||
'type': 'prompt'
|
||||
})
|
||||
const prompt = new Confirm({model});
|
||||
prompt.show();
|
||||
let result;
|
||||
try {
|
||||
result = (await prompt.confirmation).pop()?.value;
|
||||
} catch (e) {
|
||||
result = false;
|
||||
}
|
||||
prompt.remove();
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show an alert modal to the user.
|
||||
* @method _converse.api.alert
|
||||
* @param { ('info'|'warn'|'error') } type - The type of alert.
|
||||
* @param { String } title - The header text for the alert.
|
||||
* @param { (String[]|String) } messages - The alert text to show to the user.
|
||||
*/
|
||||
alert (type, title, messages) {
|
||||
if (typeof messages === 'string') {
|
||||
messages = [messages];
|
||||
}
|
||||
let level;
|
||||
if (type === 'error') {
|
||||
level = 'alert-danger';
|
||||
} else if (type === 'info') {
|
||||
level = 'alert-info';
|
||||
} else if (type === 'warn') {
|
||||
level = 'alert-warning';
|
||||
}
|
||||
|
||||
if (alert === undefined) {
|
||||
const model = new Model({
|
||||
'title': title,
|
||||
'messages': messages,
|
||||
'level': level,
|
||||
'type': 'alert'
|
||||
})
|
||||
alert = new Alert({model});
|
||||
} else {
|
||||
alert.model.set({
|
||||
'title': title,
|
||||
'messages': messages,
|
||||
'level': level
|
||||
});
|
||||
}
|
||||
alert.show();
|
||||
}
|
||||
});
|
||||
Object.assign(_converse.api, modal_api);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3,22 +3,16 @@
|
||||
* @copyright The Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import "modals/profile.js";
|
||||
import "modals/chat-status.js";
|
||||
import "@converse/headless/converse-status";
|
||||
import "@converse/headless/converse-vcard";
|
||||
import "converse-modal";
|
||||
import UserSettingsModal from "modals/user-settings";
|
||||
import bootstrap from "bootstrap.native";
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from 'sizzle';
|
||||
import tpl_chat_status_modal from "templates/chat_status_modal";
|
||||
import tpl_profile from "templates/profile.js";
|
||||
import tpl_profile_modal from "templates/profile_modal";
|
||||
import { BootstrapModal } from "./converse-modal.js";
|
||||
import { __ } from './i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
converse.plugins.add('converse-profile', {
|
||||
|
||||
@ -35,146 +29,6 @@ converse.plugins.add('converse-profile', {
|
||||
});
|
||||
|
||||
|
||||
_converse.ProfileModal = BootstrapModal.extend({
|
||||
id: "user-profile-modal",
|
||||
events: {
|
||||
'submit .profile-form': 'onFormSubmitted'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
/**
|
||||
* Triggered when the _converse.ProfileModal has been created and initialized.
|
||||
* @event _converse#profileModalInitialized
|
||||
* @type { _converse.XMPPStatus }
|
||||
* @example _converse.api.listen.on('profileModalInitialized', status => { ... });
|
||||
*/
|
||||
api.trigger('profileModalInitialized', this.model);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_profile_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(),
|
||||
this.getAvatarData(),
|
||||
{ 'view': this }
|
||||
));
|
||||
},
|
||||
|
||||
getAvatarData () {
|
||||
const image_type = this.model.vcard.get('image_type');
|
||||
const image_data = this.model.vcard.get('image');
|
||||
const image = "data:" + image_type + ";base64," + image_data;
|
||||
return {
|
||||
'height': 128,
|
||||
'width': 128,
|
||||
image,
|
||||
};
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
this.tabs = sizzle('.nav-item .nav-link', this.el).map(e => new bootstrap.Tab(e));
|
||||
},
|
||||
|
||||
async setVCard (data) {
|
||||
try {
|
||||
await api.vcard.set(_converse.bare_jid, data);
|
||||
} catch (err) {
|
||||
log.fatal(err);
|
||||
this.alert([
|
||||
__("Sorry, an error happened while trying to save your profile data."),
|
||||
__("You can check your browser's developer console for any error output.")
|
||||
].join(" "));
|
||||
return;
|
||||
}
|
||||
this.modal.hide();
|
||||
},
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
const reader = new FileReader();
|
||||
const form_data = new FormData(ev.target);
|
||||
const image_file = form_data.get('image');
|
||||
const data = {
|
||||
'fn': form_data.get('fn'),
|
||||
'nickname': form_data.get('nickname'),
|
||||
'role': form_data.get('role'),
|
||||
'email': form_data.get('email'),
|
||||
'url': form_data.get('url'),
|
||||
};
|
||||
if (!image_file.size) {
|
||||
Object.assign(data, {
|
||||
'image': this.model.vcard.get('image'),
|
||||
'image_type': this.model.vcard.get('image_type')
|
||||
});
|
||||
this.setVCard(data);
|
||||
} else {
|
||||
reader.onloadend = () => {
|
||||
Object.assign(data, {
|
||||
'image': btoa(reader.result),
|
||||
'image_type': image_file.type
|
||||
});
|
||||
this.setVCard(data);
|
||||
};
|
||||
reader.readAsBinaryString(image_file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_converse.ChatStatusModal = BootstrapModal.extend({
|
||||
id: "modal-status-change",
|
||||
events: {
|
||||
"submit form#set-xmpp-status": "onFormSubmitted",
|
||||
"click .clear-input": "clearStatusMessage"
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_chat_status_modal(
|
||||
Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(), {
|
||||
'label_away': __('Away'),
|
||||
'label_busy': __('Busy'),
|
||||
'label_cancel': __('Cancel'),
|
||||
'label_close': __('Close'),
|
||||
'label_custom_status': __('Custom status'),
|
||||
'label_offline': __('Offline'),
|
||||
'label_online': __('Online'),
|
||||
'label_save': __('Save'),
|
||||
'label_xa': __('Away for long'),
|
||||
'modal_title': __('Change chat status'),
|
||||
'placeholder_status_message': __('Personal status message')
|
||||
}));
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
this.el.addEventListener('shown.bs.modal', () => {
|
||||
this.el.querySelector('input[name="status_message"]').focus();
|
||||
}, false);
|
||||
},
|
||||
|
||||
clearStatusMessage (ev) {
|
||||
if (ev && ev.preventDefault) {
|
||||
ev.preventDefault();
|
||||
u.hideElement(this.el.querySelector('.clear-input'));
|
||||
}
|
||||
const roster_filter = this.el.querySelector('input[name="status_message"]');
|
||||
roster_filter.value = '';
|
||||
},
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target);
|
||||
this.model.save({
|
||||
'status_message': data.get('status_message'),
|
||||
'status': data.get('chat_status')
|
||||
});
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
|
||||
_converse.XMPPStatusView = _converse.ViewWithAvatar.extend({
|
||||
tagName: "div",
|
||||
events: {
|
||||
|
@ -6,23 +6,21 @@
|
||||
import "@converse/headless/converse-chatboxes";
|
||||
import "@converse/headless/converse-roster";
|
||||
import "converse-modal";
|
||||
import "modals/add-contact.js";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_add_contact_modal from "templates/add_contact_modal.js";
|
||||
import tpl_group_header from "templates/group_header.html";
|
||||
import tpl_pending_contact from "templates/pending_contact.html";
|
||||
import tpl_requesting_contact from "templates/requesting_contact.html";
|
||||
import tpl_roster from "templates/roster.html";
|
||||
import tpl_roster_filter from "templates/roster_filter.js";
|
||||
import tpl_roster_item from "templates/roster_item.html";
|
||||
import { BootstrapModal } from "./converse-modal.js";
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
import { OrderedListView } from "@converse/skeletor/src/overview";
|
||||
import { View } from '@converse/skeletor/src/view.js';
|
||||
import { __ } from './i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
import { compact, debounce, has, without } from "lodash-es";
|
||||
import { debounce, has, without } from "lodash-es";
|
||||
|
||||
const { Strophe } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
@ -55,136 +53,6 @@ converse.plugins.add('converse-rosterview', {
|
||||
};
|
||||
|
||||
|
||||
_converse.AddContactModal = BootstrapModal.extend({
|
||||
id: "add-contact-modal",
|
||||
events: {
|
||||
'submit form': 'addContactFromForm'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
const label_nickname = api.settings.get('xhr_user_search_url') ? __('Contact name') : __('Optional nickname');
|
||||
return tpl_add_contact_modal(Object.assign(this.model.toJSON(), { _converse, label_nickname }));
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
if (typeof api.settings.get('xhr_user_search_url') === 'string') {
|
||||
this.initXHRAutoComplete();
|
||||
} else {
|
||||
this.initJIDAutoComplete();
|
||||
}
|
||||
const jid_input = this.el.querySelector('input[name="jid"]');
|
||||
this.el.addEventListener('shown.bs.modal', () => jid_input.focus(), false);
|
||||
},
|
||||
|
||||
initJIDAutoComplete () {
|
||||
if (!api.settings.get('autocomplete_add_contact')) {
|
||||
return;
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__jid').parentElement;
|
||||
this.jid_auto_complete = new _converse.AutoComplete(el, {
|
||||
'data': (text, input) => `${input.slice(0, input.indexOf("@"))}@${text}`,
|
||||
'filter': _converse.FILTER_STARTSWITH,
|
||||
'list': [...new Set(_converse.roster.map(item => Strophe.getDomainFromJid(item.get('jid'))))]
|
||||
});
|
||||
},
|
||||
|
||||
initXHRAutoComplete () {
|
||||
if (!api.settings.get('autocomplete_add_contact')) {
|
||||
return this.initXHRFetch();
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__name').parentElement;
|
||||
this.name_auto_complete = new _converse.AutoComplete(el, {
|
||||
'auto_evaluate': false,
|
||||
'filter': _converse.FILTER_STARTSWITH,
|
||||
'list': []
|
||||
});
|
||||
const xhr = new window.XMLHttpRequest();
|
||||
// `open` must be called after `onload` for mock/testing purposes.
|
||||
xhr.onload = () => {
|
||||
if (xhr.responseText) {
|
||||
const r = xhr.responseText;
|
||||
this.name_auto_complete.list = JSON.parse(r).map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
|
||||
this.name_auto_complete.auto_completing = true;
|
||||
this.name_auto_complete.evaluate();
|
||||
}
|
||||
};
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
input_el.addEventListener('input', debounce(() => {
|
||||
xhr.open("GET", `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
|
||||
xhr.send()
|
||||
} , 300));
|
||||
this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
|
||||
this.el.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.el.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
});
|
||||
},
|
||||
|
||||
initXHRFetch () {
|
||||
this.xhr = new window.XMLHttpRequest();
|
||||
this.xhr.onload = () => {
|
||||
if (this.xhr.responseText) {
|
||||
const r = this.xhr.responseText;
|
||||
const list = JSON.parse(r).map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
|
||||
if (list.length !== 1) {
|
||||
const el = this.el.querySelector('.invalid-feedback');
|
||||
el.textContent = __('Sorry, could not find a contact with that name')
|
||||
u.addClass('d-block', el);
|
||||
return;
|
||||
}
|
||||
const jid = list[0].value;
|
||||
if (this.validateSubmission(jid)) {
|
||||
const form = this.el.querySelector('form');
|
||||
const name = list[0].label;
|
||||
this.afterSubmission(form, jid, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
validateSubmission (jid) {
|
||||
const el = this.el.querySelector('.invalid-feedback');
|
||||
if (!jid || compact(jid.split('@')).length < 2) {
|
||||
u.addClass('is-invalid', this.el.querySelector('input[name="jid"]'));
|
||||
u.addClass('d-block', el);
|
||||
return false;
|
||||
} else if (_converse.roster.get(Strophe.getBareJidFromJid(jid))) {
|
||||
el.textContent = __('This contact has already been added')
|
||||
u.addClass('d-block', el);
|
||||
return false;
|
||||
}
|
||||
u.removeClass('d-block', el);
|
||||
return true;
|
||||
},
|
||||
|
||||
afterSubmission (form, jid, name) {
|
||||
_converse.roster.addAndSubscribe(jid, name);
|
||||
this.model.clear();
|
||||
this.modal.hide();
|
||||
},
|
||||
|
||||
addContactFromForm (ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target),
|
||||
jid = (data.get('jid') || '').trim();
|
||||
|
||||
if (!jid && typeof api.settings.get('xhr_user_search_url') === 'string') {
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
this.xhr.open("GET", `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
|
||||
this.xhr.send()
|
||||
return;
|
||||
}
|
||||
if (this.validateSubmission(jid)) {
|
||||
this.afterSubmission(ev.target, jid, data.get('name'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_converse.RosterFilter = Model.extend({
|
||||
initialize () {
|
||||
this.set({
|
||||
@ -195,6 +63,7 @@ converse.plugins.add('converse-rosterview', {
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
_converse.RosterFilterView = View.extend({
|
||||
tagName: 'span',
|
||||
|
||||
@ -980,4 +849,3 @@ converse.plugins.add('converse-rosterview', {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
142
src/modals/add-contact.js
Normal file
142
src/modals/add-contact.js
Normal file
@ -0,0 +1,142 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_add_contact_modal from "./templates/add-contact.js";
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
import { compact, debounce } from "lodash-es";
|
||||
|
||||
const { Strophe } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
const AddContactModal = BootstrapModal.extend({
|
||||
id: "add-contact-modal",
|
||||
events: {
|
||||
'submit form': 'addContactFromForm'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
const label_nickname = api.settings.get('xhr_user_search_url') ? __('Contact name') : __('Optional nickname');
|
||||
return tpl_add_contact_modal(Object.assign(this.model.toJSON(), { _converse, label_nickname }));
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
if (typeof api.settings.get('xhr_user_search_url') === 'string') {
|
||||
this.initXHRAutoComplete();
|
||||
} else {
|
||||
this.initJIDAutoComplete();
|
||||
}
|
||||
const jid_input = this.el.querySelector('input[name="jid"]');
|
||||
this.el.addEventListener('shown.bs.modal', () => jid_input.focus(), false);
|
||||
},
|
||||
|
||||
initJIDAutoComplete () {
|
||||
if (!api.settings.get('autocomplete_add_contact')) {
|
||||
return;
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__jid').parentElement;
|
||||
this.jid_auto_complete = new _converse.AutoComplete(el, {
|
||||
'data': (text, input) => `${input.slice(0, input.indexOf("@"))}@${text}`,
|
||||
'filter': _converse.FILTER_STARTSWITH,
|
||||
'list': [...new Set(_converse.roster.map(item => Strophe.getDomainFromJid(item.get('jid'))))]
|
||||
});
|
||||
},
|
||||
|
||||
initXHRAutoComplete () {
|
||||
if (!api.settings.get('autocomplete_add_contact')) {
|
||||
return this.initXHRFetch();
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__name').parentElement;
|
||||
this.name_auto_complete = new _converse.AutoComplete(el, {
|
||||
'auto_evaluate': false,
|
||||
'filter': _converse.FILTER_STARTSWITH,
|
||||
'list': []
|
||||
});
|
||||
const xhr = new window.XMLHttpRequest();
|
||||
// `open` must be called after `onload` for mock/testing purposes.
|
||||
xhr.onload = () => {
|
||||
if (xhr.responseText) {
|
||||
const r = xhr.responseText;
|
||||
this.name_auto_complete.list = JSON.parse(r).map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
|
||||
this.name_auto_complete.auto_completing = true;
|
||||
this.name_auto_complete.evaluate();
|
||||
}
|
||||
};
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
input_el.addEventListener('input', debounce(() => {
|
||||
xhr.open("GET", `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
|
||||
xhr.send()
|
||||
} , 300));
|
||||
this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
|
||||
this.el.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.el.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
});
|
||||
},
|
||||
|
||||
initXHRFetch () {
|
||||
this.xhr = new window.XMLHttpRequest();
|
||||
this.xhr.onload = () => {
|
||||
if (this.xhr.responseText) {
|
||||
const r = this.xhr.responseText;
|
||||
const list = JSON.parse(r).map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
|
||||
if (list.length !== 1) {
|
||||
const el = this.el.querySelector('.invalid-feedback');
|
||||
el.textContent = __('Sorry, could not find a contact with that name')
|
||||
u.addClass('d-block', el);
|
||||
return;
|
||||
}
|
||||
const jid = list[0].value;
|
||||
if (this.validateSubmission(jid)) {
|
||||
const form = this.el.querySelector('form');
|
||||
const name = list[0].label;
|
||||
this.afterSubmission(form, jid, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
validateSubmission (jid) {
|
||||
const el = this.el.querySelector('.invalid-feedback');
|
||||
if (!jid || compact(jid.split('@')).length < 2) {
|
||||
u.addClass('is-invalid', this.el.querySelector('input[name="jid"]'));
|
||||
u.addClass('d-block', el);
|
||||
return false;
|
||||
} else if (_converse.roster.get(Strophe.getBareJidFromJid(jid))) {
|
||||
el.textContent = __('This contact has already been added')
|
||||
u.addClass('d-block', el);
|
||||
return false;
|
||||
}
|
||||
u.removeClass('d-block', el);
|
||||
return true;
|
||||
},
|
||||
|
||||
afterSubmission (form, jid, name) {
|
||||
_converse.roster.addAndSubscribe(jid, name);
|
||||
this.model.clear();
|
||||
this.modal.hide();
|
||||
},
|
||||
|
||||
addContactFromForm (ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target),
|
||||
jid = (data.get('jid') || '').trim();
|
||||
|
||||
if (!jid && typeof api.settings.get('xhr_user_search_url') === 'string') {
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
this.xhr.open("GET", `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(input_el.value)}`, true);
|
||||
this.xhr.send()
|
||||
return;
|
||||
}
|
||||
if (this.validateSubmission(jid)) {
|
||||
this.afterSubmission(ev.target, jid, data.get('name'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_converse.AddContactModal = AddContactModal;
|
||||
|
||||
export default AddContactModal;
|
@ -1,5 +1,5 @@
|
||||
import tpl_add_chatroom_modal from "templates/add_chatroom_modal.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import tpl_add_muc from "./templates/add-muc.js";
|
||||
import BootstrapModal from "./base.js";
|
||||
import { Strophe } from 'strophe.js/src/strophe';
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
@ -8,6 +8,7 @@ const u = converse.env.utils;
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
persistent: true,
|
||||
id: 'add-chatroom-modal',
|
||||
|
||||
events: {
|
||||
@ -28,7 +29,7 @@ export default BootstrapModal.extend({
|
||||
const muc_domain = this.model.get('muc_domain') || api.settings.get('muc_domain');
|
||||
placeholder = muc_domain ? `name@${muc_domain}` : __('name@conference.example.org');
|
||||
}
|
||||
return tpl_add_chatroom_modal(Object.assign(this.model.toJSON(), {
|
||||
return tpl_add_muc(Object.assign(this.model.toJSON(), {
|
||||
'_converse': _converse,
|
||||
'label_room_address': api.settings.get('muc_domain') ? __('Groupchat name') : __('Groupchat address'),
|
||||
'chatroom_placeholder': placeholder,
|
||||
|
18
src/modals/alert.js
Normal file
18
src/modals/alert.js
Normal file
@ -0,0 +1,18 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_alert_modal from "./templates/alert.js";
|
||||
import { __ } from '../i18n';
|
||||
|
||||
|
||||
const Alert = BootstrapModal.extend({
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render)
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_alert_modal(Object.assign({__}, this.model.toJSON()));
|
||||
}
|
||||
});
|
||||
|
||||
export default Alert;
|
84
src/modals/base.js
Normal file
84
src/modals/base.js
Normal file
@ -0,0 +1,84 @@
|
||||
import bootstrap from "bootstrap.native";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_alert_component from "templates/alert.js";
|
||||
import { View } from '@converse/skeletor/src/view.js';
|
||||
import { _converse, converse } from "@converse/headless/converse-core";
|
||||
import { render } from 'lit-html';
|
||||
|
||||
const { sizzle } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
const BaseModal = View.extend({
|
||||
className: "modal",
|
||||
persistent: false, // Whether this modal should persist in the DOM once it's been closed
|
||||
events: {
|
||||
'click .nav-item .nav-link': 'switchTab'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.render()
|
||||
|
||||
this.el.setAttribute('tabindex', '-1');
|
||||
this.el.setAttribute('role', 'dialog');
|
||||
this.el.setAttribute('aria-hidden', 'true');
|
||||
const label_id = this.el.querySelector('.modal-title').getAttribute('id');
|
||||
label_id && this.el.setAttribute('aria-labelledby', label_id);
|
||||
|
||||
this.insertIntoDOM();
|
||||
const Modal = bootstrap.Modal;
|
||||
this.modal = new Modal(this.el, {
|
||||
backdrop: true,
|
||||
keyboard: true
|
||||
});
|
||||
this.el.addEventListener('hide.bs.modal', () => this.onHide(), false);
|
||||
},
|
||||
|
||||
onHide () {
|
||||
u.removeClass('selected', this.trigger_el);
|
||||
!this.persistent && this.remove();
|
||||
},
|
||||
|
||||
insertIntoDOM () {
|
||||
const container_el = _converse.chatboxviews.el.querySelector("#converse-modals");
|
||||
container_el.insertAdjacentElement('beforeEnd', this.el);
|
||||
},
|
||||
|
||||
switchTab (ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
sizzle('.nav-link.active', this.el).forEach(el => {
|
||||
u.removeClass('active', this.el.querySelector(el.getAttribute('href')));
|
||||
u.removeClass('active', el);
|
||||
});
|
||||
u.addClass('active', ev.target);
|
||||
u.addClass('active', this.el.querySelector(ev.target.getAttribute('href')))
|
||||
},
|
||||
|
||||
alert (message, type='primary') {
|
||||
const body = this.el.querySelector('.modal-alert');
|
||||
if (body === null) {
|
||||
log.error("Could not find a .modal-alert element in the modal to show an alert message in!");
|
||||
return;
|
||||
}
|
||||
// FIXME: Instead of adding the alert imperatively, we should
|
||||
// find a way to let the modal rerender with an alert message
|
||||
render(tpl_alert_component({'type': `alert-${type}`, 'message': message}), body);
|
||||
const el = body.firstElementChild;
|
||||
setTimeout(() => {
|
||||
u.addClass('fade-out', el);
|
||||
setTimeout(() => u.removeElement(el), 600);
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
show (ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
this.trigger_el = ev.target;
|
||||
this.trigger_el.classList.add('selected');
|
||||
}
|
||||
this.modal.show();
|
||||
}
|
||||
});
|
||||
|
||||
export default BaseModal;
|
64
src/modals/chat-status.js
Normal file
64
src/modals/chat-status.js
Normal file
@ -0,0 +1,64 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_chat_status_modal from "./templates/chat-status.js";
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, converse } from "@converse/headless/converse-core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
const ChatStatusModal = BootstrapModal.extend({
|
||||
id: "modal-status-change",
|
||||
events: {
|
||||
"submit form#set-xmpp-status": "onFormSubmitted",
|
||||
"click .clear-input": "clearStatusMessage"
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_chat_status_modal(
|
||||
Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(), {
|
||||
'label_away': __('Away'),
|
||||
'label_busy': __('Busy'),
|
||||
'label_cancel': __('Cancel'),
|
||||
'label_close': __('Close'),
|
||||
'label_custom_status': __('Custom status'),
|
||||
'label_offline': __('Offline'),
|
||||
'label_online': __('Online'),
|
||||
'label_save': __('Save'),
|
||||
'label_xa': __('Away for long'),
|
||||
'modal_title': __('Change chat status'),
|
||||
'placeholder_status_message': __('Personal status message')
|
||||
}));
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
this.el.addEventListener('shown.bs.modal', () => {
|
||||
this.el.querySelector('input[name="status_message"]').focus();
|
||||
}, false);
|
||||
},
|
||||
|
||||
clearStatusMessage (ev) {
|
||||
if (ev && ev.preventDefault) {
|
||||
ev.preventDefault();
|
||||
u.hideElement(this.el.querySelector('.clear-input'));
|
||||
}
|
||||
const roster_filter = this.el.querySelector('input[name="status_message"]');
|
||||
roster_filter.value = '';
|
||||
},
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target);
|
||||
this.model.save({
|
||||
'status_message': data.get('status_message'),
|
||||
'status': data.get('chat_status')
|
||||
});
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_converse.ChatStatusModal = ChatStatusModal;
|
||||
|
||||
export default ChatStatusModal;
|
59
src/modals/confirm.js
Normal file
59
src/modals/confirm.js
Normal file
@ -0,0 +1,59 @@
|
||||
import BootstrapModal from './base.js';
|
||||
import tpl_prompt from "./templates/prompt.js";
|
||||
import { converse } from "@converse/headless/converse-core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
const Confirm = BootstrapModal.extend({
|
||||
events: {
|
||||
'submit .confirm': 'onConfimation'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.confirmation = u.getResolveablePromise();
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render)
|
||||
this.el.addEventListener('closed.bs.modal', () => this.confirmation.reject(), false);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_prompt(this.model.toJSON());
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
if (!this.close_handler_registered) {
|
||||
this.el.addEventListener('closed.bs.modal', () => {
|
||||
if (!this.confirmation.isResolved) {
|
||||
this.confirmation.reject()
|
||||
}
|
||||
}, false);
|
||||
this.close_handler_registered = true;
|
||||
}
|
||||
},
|
||||
|
||||
onConfimation (ev) {
|
||||
ev.preventDefault();
|
||||
const form_data = new FormData(ev.target);
|
||||
const fields = (this.model.get('fields') || [])
|
||||
.map(field => {
|
||||
const value = form_data.get(field.name).trim();
|
||||
field.value = value;
|
||||
if (field.challenge) {
|
||||
field.challenge_failed = (value !== field.challenge);
|
||||
}
|
||||
return field;
|
||||
});
|
||||
|
||||
if (fields.filter(c => c.challenge_failed).length) {
|
||||
this.model.set('fields', fields);
|
||||
// Setting an array doesn't trigger a change event
|
||||
this.model.trigger('change');
|
||||
return;
|
||||
}
|
||||
this.confirmation.resolve(fields);
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
|
||||
export default Confirm;
|
@ -1,5 +1,5 @@
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import tpl_image_modal from "../templates/image_modal.js";
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_image_modal from "./templates/image.js";
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import tpl_message_versions_modal from "../templates/message_versions_modal.js";
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_message_versions_modal from "./templates/message-versions.js";
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
// FIXME: this isn't globally unique
|
||||
id: "message-versions-modal",
|
||||
|
||||
toHTML () {
|
||||
return tpl_message_versions_modal(this.model.toJSON());
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from "sizzle";
|
||||
import tpl_moderator_tools_modal from "../templates/moderator_tools_modal.js";
|
||||
import tpl_moderator_tools_modal from "./templates/moderator-tools.js";
|
||||
import { AFFILIATIONS, ROLES } from "@converse/headless/converse-muc.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import { __ } from '../i18n';
|
||||
import { api, converse } from "@converse/headless/converse-core";
|
||||
|
||||
@ -12,7 +12,7 @@ let _converse;
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
id: "converse-modtools-modal",
|
||||
persistent: true,
|
||||
|
||||
initialize (attrs) {
|
||||
_converse = attrs._converse;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import BootstrapModal from "./base.js";
|
||||
import { __ } from '../i18n';
|
||||
import { api, converse } from "@converse/headless/converse-core";
|
||||
import log from "@converse/headless/log";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_muc_details from "./templates/muc-details.js";
|
||||
import { __ } from '../i18n';
|
||||
import tpl_chatroom_details_modal from "../templates/chatroom_details_modal.js";
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
@ -15,7 +15,7 @@ export default BootstrapModal.extend({
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_chatroom_details_modal(Object.assign(
|
||||
return tpl_muc_details(Object.assign(
|
||||
this.model.toJSON(), {
|
||||
'config': this.model.config.toJSON(),
|
||||
'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import tpl_muc_invite_modal from "templates/muc_invite_modal.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_muc_invite_modal from "./templates/muc-invite.js";
|
||||
import { _converse, converse } from "@converse/headless/converse-core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
@ -49,5 +49,3 @@ export default BootstrapModal.extend({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from 'sizzle';
|
||||
import st from "@converse/headless/utils/stanza";
|
||||
import tpl_list_chatrooms_modal from "templates/list_chatrooms_modal.js";
|
||||
import tpl_list_chatrooms_modal from "./templates/muc-list.js";
|
||||
import tpl_room_description from "templates/room_description.html";
|
||||
import tpl_spinner from "templates/spinner.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import { Strophe, $iq } from 'strophe.js/src/strophe';
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
@ -83,6 +83,7 @@ function toggleRoomInfo (ev) {
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
id: "list-chatrooms-modal",
|
||||
persistent: true,
|
||||
|
||||
initialize () {
|
||||
this.items = [];
|
||||
|
@ -1,20 +1,19 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_occupant_modal from "./templates/occupant.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import { _converse, api } from "@converse/headless/converse-core";
|
||||
|
||||
|
||||
const OccupantModal = BootstrapModal.extend({
|
||||
id: "muc-occupant-modal",
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
/**
|
||||
* Triggered once the OccupantModal has been initialized
|
||||
* @event _converse#userDetailsModalInitialized
|
||||
* @type { _converse.ChatBox }
|
||||
* @example _converse.api.listen.on('userDetailsModalInitialized', chatbox => { ... });
|
||||
*/
|
||||
* Triggered once the OccupantModal has been initialized
|
||||
* @event _converse#userDetailsModalInitialized
|
||||
* @type { _converse.ChatBox }
|
||||
* @example _converse.api.listen.on('userDetailsModalInitialized', chatbox => { ... });
|
||||
*/
|
||||
api.trigger('occupantModalInitialized', this.model);
|
||||
},
|
||||
|
||||
|
99
src/modals/profile.js
Normal file
99
src/modals/profile.js
Normal file
@ -0,0 +1,99 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import bootstrap from "bootstrap.native";
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from 'sizzle';
|
||||
import tpl_profile_modal from "./templates/profile.js";
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, api } from "@converse/headless/converse-core";
|
||||
|
||||
|
||||
const ProfileModal = BootstrapModal.extend({
|
||||
id: "user-profile-modal",
|
||||
events: {
|
||||
'submit .profile-form': 'onFormSubmitted'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
/**
|
||||
* Triggered when the _converse.ProfileModal has been created and initialized.
|
||||
* @event _converse#profileModalInitialized
|
||||
* @type { _converse.XMPPStatus }
|
||||
* @example _converse.api.listen.on('profileModalInitialized', status => { ... });
|
||||
*/
|
||||
api.trigger('profileModalInitialized', this.model);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_profile_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(),
|
||||
this.getAvatarData(),
|
||||
{ 'view': this }
|
||||
));
|
||||
},
|
||||
|
||||
getAvatarData () {
|
||||
const image_type = this.model.vcard.get('image_type');
|
||||
const image_data = this.model.vcard.get('image');
|
||||
const image = "data:" + image_type + ";base64," + image_data;
|
||||
return {
|
||||
'height': 128,
|
||||
'width': 128,
|
||||
image,
|
||||
};
|
||||
},
|
||||
|
||||
afterRender () {
|
||||
this.tabs = sizzle('.nav-item .nav-link', this.el).map(e => new bootstrap.Tab(e));
|
||||
},
|
||||
|
||||
async setVCard (data) {
|
||||
try {
|
||||
await api.vcard.set(_converse.bare_jid, data);
|
||||
} catch (err) {
|
||||
log.fatal(err);
|
||||
this.alert([
|
||||
__("Sorry, an error happened while trying to save your profile data."),
|
||||
__("You can check your browser's developer console for any error output.")
|
||||
].join(" "));
|
||||
return;
|
||||
}
|
||||
this.modal.hide();
|
||||
},
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
const reader = new FileReader();
|
||||
const form_data = new FormData(ev.target);
|
||||
const image_file = form_data.get('image');
|
||||
const data = {
|
||||
'fn': form_data.get('fn'),
|
||||
'nickname': form_data.get('nickname'),
|
||||
'role': form_data.get('role'),
|
||||
'email': form_data.get('email'),
|
||||
'url': form_data.get('url'),
|
||||
};
|
||||
if (!image_file.size) {
|
||||
Object.assign(data, {
|
||||
'image': this.model.vcard.get('image'),
|
||||
'image_type': this.model.vcard.get('image_type')
|
||||
});
|
||||
this.setVCard(data);
|
||||
} else {
|
||||
reader.onloadend = () => {
|
||||
Object.assign(data, {
|
||||
'image': btoa(reader.result),
|
||||
'image_type': image_file.type
|
||||
});
|
||||
this.setVCard(data);
|
||||
};
|
||||
reader.readAsBinaryString(image_file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_converse.ProfileModal = ProfileModal;
|
||||
|
||||
export default ProfileModal;
|
@ -1,6 +1,6 @@
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '../i18n';
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { __ } from '../../i18n';
|
||||
import { modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
export default (o) => {
|
@ -1,5 +1,5 @@
|
||||
import xss from "xss/dist/xss";
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from '../../i18n';
|
||||
import { html } from "lit-html";
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
|
@ -1,5 +1,5 @@
|
||||
import { html } from "lit-html";
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
export default (o) => html`
|
@ -1,8 +1,7 @@
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from '../../i18n';
|
||||
import { html } from "lit-html";
|
||||
|
||||
|
||||
export const modal_close_button = html`<button type="button" class="btn btn-secondary" data-dismiss="modal">${__('Close')}</button>`;
|
||||
|
||||
export const modal_header_close_button = html`<button type="button" class="close" data-dismiss="modal" aria-label="${__('Close')}"><span aria-hidden="true">×</span></button>`;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { html } from "lit-html";
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
export default (o) => html`
|
@ -1,6 +1,6 @@
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '../i18n';
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons"
|
||||
import { __ } from '../../i18n';
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
export default (o) => {
|
@ -1,7 +1,7 @@
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '../i18n';
|
||||
import dayjs from 'dayjs';
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons"
|
||||
import { __ } from '../../i18n';
|
||||
import { html } from "lit-html";
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
export default (o) => html`
|
@ -1,7 +1,7 @@
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '../i18n';
|
||||
import spinner from "./spinner.js";
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { __ } from '../../i18n';
|
||||
import spinner from "../../templates/spinner.js";
|
||||
import { modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
function getRoleHelpText (role) {
|
@ -1,6 +1,6 @@
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from '../../i18n';
|
||||
import { html } from "lit-html";
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons"
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons.js"
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
import xss from "xss/dist/xss";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '../i18n';
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { __ } from '../../i18n';
|
||||
import { modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
export default (o) => {
|
@ -1,8 +1,8 @@
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from '../../i18n';
|
||||
import { html } from "lit-html";
|
||||
import { repeat } from 'lit-html/directives/repeat.js';
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons"
|
||||
import spinner from "./spinner.js";
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons.js"
|
||||
import spinner from "../../templates/spinner.js";
|
||||
|
||||
|
||||
const form = (o) => {
|
@ -1,5 +1,5 @@
|
||||
import { html } from "lit-html";
|
||||
import { modal_close_button, modal_header_close_button } from "../../templates/buttons"
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons.js"
|
||||
import { renderAvatar } from '../../templates/directives/avatar';
|
||||
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import "../components/image_picker.js";
|
||||
import spinner from "./spinner.js";
|
||||
import { __ } from '../i18n';
|
||||
import "../../components/image_picker.js";
|
||||
import spinner from "../../templates/spinner.js";
|
||||
import { __ } from '../../i18n';
|
||||
import { _converse, converse } from "@converse/headless/converse-core";
|
||||
import { html } from "lit-html";
|
||||
import { modal_header_close_button } from "./buttons";
|
||||
import { modal_header_close_button } from "./buttons.js";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from '../../i18n';
|
||||
|
||||
|
||||
const tpl_field = (f) => html`
|
@ -1,7 +1,7 @@
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from '../../i18n';
|
||||
import { html } from "lit-html";
|
||||
import avatar from "./avatar.js";
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons"
|
||||
import avatar from "../../templates/avatar.js";
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons.js"
|
||||
|
||||
|
||||
const device_fingerprint = (o) => {
|
@ -1,9 +1,9 @@
|
||||
import '../components/adhoc-commands.js';
|
||||
import '../../components/adhoc-commands.js';
|
||||
import xss from "xss/dist/xss";
|
||||
import { __ } from '../i18n';
|
||||
import { __ } from '../../i18n';
|
||||
import { api } from "@converse/headless/converse-core";
|
||||
import { html } from "lit-html";
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { modal_header_close_button } from "./buttons.js"
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import BootstrapModal from "./base.js";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_user_details_modal from "../templates/user_details_modal.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import tpl_user_details_modal from "./templates/user-details.js";
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
|
||||
@ -8,7 +8,7 @@ const u = converse.env.utils;
|
||||
|
||||
|
||||
const UserDetailsModal = BootstrapModal.extend({
|
||||
id: "user-details-modal",
|
||||
persistent: true,
|
||||
|
||||
events: {
|
||||
'click button.refresh-contact': 'refreshContact',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import tpl_user_settings_modal from "templates/user_settings_modal";
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_user_settings_modal from "./templates/user-settings.js";
|
||||
|
||||
let _converse;
|
||||
|
||||
@ -21,4 +21,3 @@ export default BootstrapModal.extend({
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user