Fixes #515 Add support for XEP-0050 Ad-Hoc commands
This commit is contained in:
parent
60b3f7ae25
commit
78b60a3bd9
@ -6,6 +6,7 @@
|
||||
configuration settings should now be accessed via `_converse.api.settings.get` and not directly on the `_converse` object.
|
||||
Soon we'll deprecate the latter, so prepare now.
|
||||
|
||||
- #515 Add support for XEP-0050 Ad-Hoc commands
|
||||
- #1313: Stylistic improvements to the send button
|
||||
- #1490: Busy-loop when fetching registration form fails
|
||||
- #1535: Add option to destroy a MUC
|
||||
|
@ -200,6 +200,7 @@ body.converse-fullscreen {
|
||||
.dropdown-item {
|
||||
padding: 0.5rem 1rem;
|
||||
.fa {
|
||||
width: 1.25em;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
&:active, &.selected {
|
||||
|
216
src/components/adhoc-commands.js
Normal file
216
src/components/adhoc-commands.js
Normal file
@ -0,0 +1,216 @@
|
||||
import "./autocomplete.js"
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { CustomElement } from './element.js';
|
||||
import { api } from "@converse/headless/converse-core";
|
||||
import { html } from "lit-html";
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from "sizzle";
|
||||
|
||||
const { Strophe, $iq } = window.converse.env;
|
||||
const u = window.converse.env.utils;
|
||||
|
||||
const i18n_hide = __('Hide');
|
||||
const i18n_choose_service = __('On which entity do you want to run commands?');
|
||||
const i18n_choose_service_instructions = __(
|
||||
'Certain XMPP services and entities allow privileged users to execute ad-hoc commands on them.');
|
||||
const i18n_commands_found = __('Commands found');
|
||||
const i18n_fetch_commands = __('List available commands');
|
||||
const i18n_jid_placeholder = __('XMPP Address');
|
||||
const i18n_no_commands_found = __('No commands found');
|
||||
const i18n_run = __('Execute');
|
||||
|
||||
|
||||
const tpl_command_form = (o, command) => html`
|
||||
<form @submit=${o.runCommand}>
|
||||
${ command.alert ? html`<div class="alert alert-${command.alert_type}" role="alert">${command.alert}</div>` : '' }
|
||||
<fieldset class="form-group">
|
||||
<input type="hidden" name="command_node" value="${command.node}"/>
|
||||
<input type="hidden" name="command_jid" value="${command.jid}"/>
|
||||
|
||||
<p class="form-help">${command.instructions}</p>
|
||||
<!-- Fields are generated internally, with xForm2webForm -->
|
||||
${ command.fields.map(field => unsafeHTML(field)) }
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<input type="submit" class="btn btn-primary" value="${i18n_run}">
|
||||
<input type="button" class="btn btn-secondary button-cancel" value="${i18n_hide}" @click=${o.hideCommandForm}>
|
||||
</fieldset>
|
||||
</form>
|
||||
`;
|
||||
|
||||
|
||||
const tpl_command = (o, command) => html`
|
||||
<li class="room-item list-group-item">
|
||||
<div class="available-chatroom d-flex flex-row">
|
||||
<a class="open-room available-room w-100"
|
||||
@click=${o.toggleCommandForm}
|
||||
data-command-node="${command.node}"
|
||||
data-command-jid="${command.jid}"
|
||||
data-command-name="${command.name}"
|
||||
title="${command.name}"
|
||||
href="#">${command.name || command.jid}</a>
|
||||
</div>
|
||||
${ command.node === o.showform ? tpl_command_form(o, command) : '' }
|
||||
</li>
|
||||
`;
|
||||
|
||||
|
||||
async function getAutoCompleteList () {
|
||||
const models = [...(await api.rooms.get()), ...(await api.contacts.get())];
|
||||
const jids = [...new Set(models.map(o => Strophe.getDomainFromJid(o.get('jid'))))];
|
||||
return jids;
|
||||
}
|
||||
|
||||
const tpl_adhoc = (o) => html`
|
||||
<form class="converse-form" @submit=${o.fetchCommands}>
|
||||
<fieldset class="form-group">
|
||||
<label>
|
||||
${i18n_choose_service}
|
||||
<p class="form-help">${i18n_choose_service_instructions}</p>
|
||||
<converse-autocomplete
|
||||
.getAutoCompleteList="${getAutoCompleteList}"
|
||||
placeholder="${i18n_jid_placeholder}"
|
||||
name="jid"/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input type="submit" class="btn btn-primary" value="${i18n_fetch_commands}">
|
||||
</fieldset>
|
||||
${ o.view === 'list-commands' ? html`
|
||||
<fieldset class="form-group">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active">${ o.commands.length ? i18n_commands_found : i18n_no_commands_found }:</li>
|
||||
${ o.commands.map(cmd => tpl_command(o, cmd)) }
|
||||
</ul>
|
||||
</fieldset>`
|
||||
: '' }
|
||||
|
||||
</form>
|
||||
`;
|
||||
|
||||
|
||||
async function fetchCommandForm (command) {
|
||||
const node = command.node;
|
||||
const jid = command.jid;
|
||||
const stanza = $iq({
|
||||
'type': 'set',
|
||||
'to': jid
|
||||
}).c('command', {
|
||||
'xmlns': Strophe.NS.ADHOC,
|
||||
'node': node,
|
||||
'action': 'execute'
|
||||
});
|
||||
try {
|
||||
const iq = await api.sendIQ(stanza);
|
||||
const cmd_el = sizzle(`command[xmlns="${Strophe.NS.ADHOC}"]`, iq).pop();
|
||||
command.sessionid = cmd_el.getAttribute('sessionid');
|
||||
command.instructions = sizzle('x[type="form"][xmlns="jabber:x:data"] instructions', cmd_el).pop()?.textContent;
|
||||
command.fields = sizzle('x[type="form"][xmlns="jabber:x:data"] field', cmd_el)
|
||||
.map(f => u.xForm2webForm(f, cmd_el));
|
||||
|
||||
} catch (e) {
|
||||
if (e === null) {
|
||||
log.error(`Error: timeout while trying to execute command for ${jid}`);
|
||||
} else {
|
||||
log.error(`Error while trying to execute command for ${jid}`);
|
||||
log.error(e);
|
||||
}
|
||||
command.fields = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AdHocCommands extends CustomElement {
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
'view': { type: String },
|
||||
'showform': { type: String },
|
||||
'nonce': { type: String } // Used to force re-rendering
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
this.view = 'choose-service';
|
||||
this.showform = '';
|
||||
this.commands = [];
|
||||
}
|
||||
|
||||
render () {
|
||||
return tpl_adhoc({
|
||||
'commands': this.commands,
|
||||
'fetchCommands': ev => this.fetchCommands(ev),
|
||||
'hideCommandForm': ev => this.hideCommandForm(ev),
|
||||
'runCommand': ev => this.runCommand(ev),
|
||||
'showform': this.showform,
|
||||
'toggleCommandForm': ev => this.toggleCommandForm(ev),
|
||||
'view': this.view,
|
||||
});
|
||||
}
|
||||
|
||||
async fetchCommands (ev) {
|
||||
ev.preventDefault();
|
||||
const form_data = new FormData(ev.target);
|
||||
const jid = form_data.get('jid').trim();
|
||||
if (await api.disco.supports(Strophe.NS.ADHOC, jid)) {
|
||||
this.commands = await api.adhoc.getCommands(jid);
|
||||
this.view = 'list-commands';
|
||||
}
|
||||
}
|
||||
|
||||
async toggleCommandForm (ev) {
|
||||
ev.preventDefault();
|
||||
const node = ev.target.getAttribute('data-command-node');
|
||||
const cmd = this.commands.filter(c => c.node === node)[0];
|
||||
this.showform !== node && await fetchCommandForm(cmd);
|
||||
this.showform = node;
|
||||
}
|
||||
|
||||
hideCommandForm (ev) {
|
||||
ev.preventDefault();
|
||||
this.showform = ''
|
||||
}
|
||||
|
||||
async runCommand (ev) {
|
||||
ev.preventDefault();
|
||||
const form_data = new FormData(ev.target);
|
||||
const jid = form_data.get('command_jid').trim();
|
||||
const node = form_data.get('command_node').trim();
|
||||
|
||||
const cmd = this.commands.filter(c => c.node === node)[0];
|
||||
const inputs = sizzle(':input:not([type=button]):not([type=submit])', ev.target);
|
||||
const configArray = inputs
|
||||
.filter(i => !['command_jid', 'command_node'].includes(i.getAttribute('name')))
|
||||
.map(u.webForm2xForm);
|
||||
|
||||
const iq = $iq({to: jid, type: "set"})
|
||||
.c("command", {
|
||||
'sessionid': cmd.session,
|
||||
'node': cmd.node,
|
||||
'xmlns': Strophe.NS.ADHOC
|
||||
}).c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
|
||||
configArray.forEach(node => iq.cnode(node).up());
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await api.sendIQ(iq);
|
||||
} catch (e) {
|
||||
cmd.alert_type = 'danger';
|
||||
cmd.alert = __('Sorry, an error occurred while trying to execute the command. See the developer console for details');
|
||||
log.error('Error while trying to execute an ad-hoc command');
|
||||
log.error(e);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
cmd.alert = result.querySelector('note')?.textContent;
|
||||
} else {
|
||||
cmd.alert = 'Done';
|
||||
}
|
||||
cmd.alert_type = 'primary';
|
||||
this.nonce = u.getUniqueId();
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('converse-adhoc-commands', AdHocCommands);
|
@ -12,6 +12,7 @@ import { debounce, head, isString, isUndefined } from "lodash";
|
||||
import { BootstrapModal } from "./converse-modal.js";
|
||||
import { render } from "lit-html";
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import RoomDetailsModal from 'modals/muc-details.js';
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import log from "@converse/headless/log";
|
||||
import st from "@converse/headless/utils/stanza";
|
||||
@ -19,7 +20,6 @@ import tpl_add_chatroom_modal from "templates/add_chatroom_modal.js";
|
||||
import tpl_chatroom from "templates/chatroom.js";
|
||||
import tpl_chatroom_bottom_panel from "templates/chatroom_bottom_panel.html";
|
||||
import tpl_chatroom_destroyed from "templates/chatroom_destroyed.html";
|
||||
import tpl_chatroom_details_modal from "templates/chatroom_details_modal.js";
|
||||
import tpl_chatroom_disconnect from "templates/chatroom_disconnect.html";
|
||||
import tpl_chatroom_head from "templates/chatroom_head.js";
|
||||
import tpl_chatroom_nickname_form from "templates/chatroom_nickname_form.html";
|
||||
@ -610,30 +610,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
});
|
||||
|
||||
|
||||
_converse.RoomDetailsModal = BootstrapModal.extend({
|
||||
id: "room-details-modal",
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.listenTo(this.model.features, 'change', this.render);
|
||||
this.listenTo(this.model.occupants, 'add', this.render);
|
||||
this.listenTo(this.model.occupants, 'change', this.render);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_chatroom_details_modal(Object.assign(
|
||||
this.model.toJSON(), {
|
||||
'config': this.model.config.toJSON(),
|
||||
'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()),
|
||||
'features': this.model.features.toJSON(),
|
||||
'num_occupants': this.model.occupants.length,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* NativeView which renders a groupchat, based upon
|
||||
* { @link _converse.ChatBoxView } for normal one-on-one chat boxes.
|
||||
@ -1080,7 +1056,7 @@ converse.plugins.add('converse-muc-views', {
|
||||
showRoomDetailsModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.model.room_details_modal === undefined) {
|
||||
this.model.room_details_modal = new _converse.RoomDetailsModal({'model': this.model});
|
||||
this.model.room_details_modal = new RoomDetailsModal({'model': this.model});
|
||||
}
|
||||
this.model.room_details_modal.show(ev);
|
||||
},
|
||||
@ -1106,20 +1082,21 @@ converse.plugins.add('converse-muc-views', {
|
||||
|
||||
/**
|
||||
* Returns a list of objects which represent buttons for the groupchat header.
|
||||
* @async
|
||||
* @emits _converse#getHeadingButtons
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#getHeadingButtons
|
||||
*/
|
||||
getHeadingButtons (subject_hidden) {
|
||||
const buttons = [{
|
||||
const buttons = [];
|
||||
buttons.push({
|
||||
'i18n_text': __('Details'),
|
||||
'i18n_title': __('Show more information about this groupchat'),
|
||||
'handler': ev => this.showRoomDetailsModal(ev),
|
||||
'a_class': 'show-room-details-modal',
|
||||
'icon_class': 'fa-info-circle',
|
||||
'name': 'details'
|
||||
}];
|
||||
});
|
||||
|
||||
if (this.model.getOwnAffiliation() === 'owner') {
|
||||
buttons.push({
|
||||
'i18n_text': __('Configure'),
|
||||
|
@ -7,12 +7,12 @@ import "@converse/headless/converse-status";
|
||||
import "@converse/headless/converse-vcard";
|
||||
import "converse-modal";
|
||||
import { BootstrapModal } from "./converse-modal.js";
|
||||
import UserSettingsModal from "modals/user-settings";
|
||||
import bootstrap from "bootstrap.native";
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from 'sizzle';
|
||||
import tpl_chat_status_modal from "templates/chat_status_modal";
|
||||
import tpl_client_info_modal from "templates/client_info_modal";
|
||||
import tpl_profile from "templates/profile.js";
|
||||
import tpl_profile_modal from "templates/profile_modal";
|
||||
|
||||
@ -182,26 +182,11 @@ converse.plugins.add('converse-profile', {
|
||||
}
|
||||
});
|
||||
|
||||
_converse.ClientInfoModal = BootstrapModal.extend({
|
||||
id: "converse-client-info-modal",
|
||||
|
||||
toHTML () {
|
||||
return tpl_client_info_modal(
|
||||
Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(),
|
||||
{ 'version_name': _converse.VERSION_NAME }
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
_converse.XMPPStatusView = _converse.ViewWithAvatar.extend({
|
||||
tagName: "div",
|
||||
events: {
|
||||
"click a.show-profile": "showProfileModal",
|
||||
"click a.change-status": "showStatusChangeModal",
|
||||
"click .show-client-info": "showClientInfoModal",
|
||||
"click .logout": "logOut"
|
||||
},
|
||||
|
||||
@ -218,8 +203,9 @@ converse.plugins.add('converse-profile', {
|
||||
_converse,
|
||||
chat_status,
|
||||
'fullname': this.model.vcard.get('fullname') || _converse.bare_jid,
|
||||
"showUserSettingsModal": ev => this.showUserSettingsModal(ev),
|
||||
'status_message': this.model.get('status_message') ||
|
||||
__("I am %1$s", this.getPrettyStatus(chat_status))
|
||||
__("I am %1$s", this.getPrettyStatus(chat_status)),
|
||||
}));
|
||||
},
|
||||
|
||||
@ -228,6 +214,7 @@ converse.plugins.add('converse-profile', {
|
||||
},
|
||||
|
||||
showProfileModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.profile_modal === undefined) {
|
||||
this.profile_modal = new _converse.ProfileModal({model: this.model});
|
||||
}
|
||||
@ -235,17 +222,19 @@ converse.plugins.add('converse-profile', {
|
||||
},
|
||||
|
||||
showStatusChangeModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.status_modal === undefined) {
|
||||
this.status_modal = new _converse.ChatStatusModal({model: this.model});
|
||||
}
|
||||
this.status_modal.show(ev);
|
||||
},
|
||||
|
||||
showClientInfoModal(ev) {
|
||||
if (this.client_info_modal === undefined) {
|
||||
this.client_info_modal = new _converse.ClientInfoModal({model: this.model});
|
||||
showUserSettingsModal(ev) {
|
||||
ev.preventDefault();
|
||||
if (this.user_settings_modal === undefined) {
|
||||
this.user_settings_modal = new UserSettingsModal({model: this.model, _converse});
|
||||
}
|
||||
this.client_info_modal.show(ev);
|
||||
this.user_settings_modal.show(ev);
|
||||
},
|
||||
|
||||
logOut (ev) {
|
||||
|
@ -7,11 +7,13 @@
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import "@converse/headless/converse-muc";
|
||||
import RoomDetailsModal from 'modals/muc-details.js';
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import tpl_rooms_list from "templates/rooms_list.js";
|
||||
import { Model } from 'skeletor.js/src/model.js';
|
||||
import { View } from 'skeletor.js/src/view.js';
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import converse from "@converse/headless/converse-core";
|
||||
import tpl_rooms_list from "templates/rooms_list.js";
|
||||
|
||||
|
||||
const { Strophe } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
@ -110,7 +112,7 @@ converse.plugins.add('converse-roomslist', {
|
||||
const room = _converse.chatboxes.get(jid);
|
||||
ev.preventDefault();
|
||||
if (room.room_details_modal === undefined) {
|
||||
room.room_details_modal = new _converse.RoomDetailsModal({'model': room});
|
||||
room.room_details_modal = new RoomDetailsModal({'model': room});
|
||||
}
|
||||
room.room_details_modal.show(ev);
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import { __, i18n } from './i18n';
|
||||
import { assignIn, debounce, invoke, isFunction, isObject, isString, pick } from 'lodash';
|
||||
import { assignIn, debounce, invoke, isElement, isFunction, isObject, isString, pick } from 'lodash';
|
||||
import { Collection } from "skeletor.js/src/collection";
|
||||
import { Events } from 'skeletor.js/src/events.js';
|
||||
import { Model } from 'skeletor.js/src/model.js';
|
||||
@ -97,6 +97,7 @@ const PROMISES = [
|
||||
// These are just the @converse/headless plugins, for the full converse,
|
||||
// the other plugins are whitelisted in src/converse.js
|
||||
const CORE_PLUGINS = [
|
||||
'converse-adhoc',
|
||||
'converse-bookmarks',
|
||||
'converse-bosh',
|
||||
'converse-caps',
|
||||
@ -307,7 +308,7 @@ function initUserSettings () {
|
||||
* @namespace _converse.api
|
||||
* @memberOf _converse
|
||||
*/
|
||||
const api = _converse.api = {
|
||||
export const api = _converse.api = {
|
||||
/**
|
||||
* This grouping collects API functions related to the XMPP connection.
|
||||
*
|
||||
@ -887,7 +888,8 @@ const api = _converse.api = {
|
||||
promise = new Promise((resolve, reject) => _converse.connection.sendIQ(stanza, resolve, reject, timeout));
|
||||
promise.catch(e => {
|
||||
if (e === null) {
|
||||
throw new TimeoutError(`Timeout error after ${timeout}ms for the following IQ stanza: ${stanza}`);
|
||||
const el = isElement(stanza) ? stanza : stanza.nodeTree;
|
||||
throw new TimeoutError(`Timeout error after ${timeout}ms for the following IQ stanza: ${el}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -2,6 +2,7 @@
|
||||
* --------------------
|
||||
* Any of the following components may be removed if they're not needed.
|
||||
*/
|
||||
import "./converse-adhoc"; // XEP-0050 Ad Hoc Commands
|
||||
import "./converse-bookmarks"; // XEP-0199 XMPP Ping
|
||||
import "./converse-bosh"; // XEP-0206 BOSH
|
||||
import "./converse-caps"; // XEP-0115 Entity Capabilities
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { api } from "@converse/headless/converse-core";
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from "sizzle";
|
||||
import tpl_muc_commands_modal from "../templates/muc_commands_modal.js";
|
||||
|
||||
const { Strophe } = window.converse.env;
|
||||
const { Strophe, $iq } = window.converse.env;
|
||||
const u = window.converse.env.utils;
|
||||
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
@ -19,8 +22,9 @@ export default BootstrapModal.extend({
|
||||
toHTML () {
|
||||
return tpl_muc_commands_modal(Object.assign(
|
||||
this.model.toJSON(), {
|
||||
'commands': this.commands,
|
||||
'display_name': __('Ad-hoc commands for %1$s', this.model.getDisplayName()),
|
||||
'commands': this.commands
|
||||
'toggleCommandForm': ev => this.toggleCommandForm(ev)
|
||||
})
|
||||
);
|
||||
},
|
||||
@ -28,5 +32,59 @@ export default BootstrapModal.extend({
|
||||
async getCommands () {
|
||||
this.commands = await api.adhoc.getCommands(Strophe.getDomainFromJid(this.model.get('jid')));
|
||||
this.render();
|
||||
},
|
||||
|
||||
async toggleCommandForm (ev) {
|
||||
ev.preventDefault();
|
||||
const node = ev.target.getAttribute('data-command-node');
|
||||
this.commands.filter(c => (c.node !== node)).forEach(c => (c.show_form = false));
|
||||
const cmd = this.commands.filter(c => c.node === node)[0];
|
||||
cmd.show_form = !cmd.show_form;
|
||||
cmd.show_form && await this.fetchCommandForm(cmd);
|
||||
this.render();
|
||||
},
|
||||
|
||||
async fetchCommandForm (command) {
|
||||
const node = command.node;
|
||||
const jid = command.jid;
|
||||
const stanza = $iq({
|
||||
'type': 'set',
|
||||
'to': jid
|
||||
}).c('command', {
|
||||
'xmlns': Strophe.NS.ADHOC,
|
||||
'node': node,
|
||||
'action': 'execute'
|
||||
});
|
||||
command.fields;
|
||||
try {
|
||||
const iq = await api.sendIQ(stanza);
|
||||
command.fields = sizzle('field', iq).map(f => u.xForm2webForm(f, iq))
|
||||
} catch (e) {
|
||||
if (e === null) {
|
||||
log.error(`Error: timeout while trying to execute command for ${jid}`);
|
||||
} else {
|
||||
log.error(`Error while trying to execute command for ${jid}`);
|
||||
log.error(e);
|
||||
}
|
||||
command.fields = [];
|
||||
}
|
||||
|
||||
/*
|
||||
<iq xmlns="jabber:client" id="72c21b57-5e9f-4b63-9e53-c6e69ed3337e:sendIQ" type="result" from="conference.chat.example.org" to="arzu.horsten@chat.example.org/converse.js-138545405">
|
||||
<command xmlns="http://jabber.org/protocol/commands" node="http://prosody.im/protocol/hats#add" sessionid="141a571b-37e2-4891-824f-72ca4b64806f" status="executing">
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<title>Add a hat</title>
|
||||
<instructions>Assign a hat to a room member</instructions>
|
||||
<field label="User JID" type="jid-single" var="user"><required/></field>
|
||||
<field label="Room JID" type="jid-single" var="room"><required/></field>
|
||||
<field label="Hat title" type="text-single" var="title"/>
|
||||
<field label="Hat URI" type="text-single" var="uri"><required/></field>
|
||||
</x>
|
||||
<actions execute="complete"><next/><complete/></actions>
|
||||
</command>
|
||||
</iq>
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
24
src/modals/user-settings.js
Normal file
24
src/modals/user-settings.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import tpl_client_info_modal from "templates/client_info_modal";
|
||||
|
||||
let _converse;
|
||||
|
||||
export default BootstrapModal.extend({
|
||||
id: "converse-client-info-modal",
|
||||
|
||||
initialize (settings) {
|
||||
_converse = settings._converse;
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_client_info_modal(
|
||||
Object.assign(
|
||||
this.model.toJSON(),
|
||||
this.model.vcard.toJSON(),
|
||||
{ 'version_name': _converse.VERSION_NAME }
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -2,10 +2,13 @@ import { __ } from '@converse/headless/i18n';
|
||||
import { html } from "lit-html";
|
||||
import { modal_header_close_button } from "./buttons"
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
import '../components/adhoc-commands.js';
|
||||
import xss from "xss/dist/xss";
|
||||
|
||||
|
||||
const modal_title = __('About');
|
||||
const i18n_modal_title = __('Settings');
|
||||
const i18n_about = __('About');
|
||||
const i18n_commands = __('Commands');
|
||||
|
||||
const first_subtitle = __(
|
||||
'%1$s Open Source %2$s XMPP chat client brought to you by %3$s Opkode %2$s',
|
||||
@ -20,21 +23,43 @@ const second_subtitle = __(
|
||||
'</a>'
|
||||
);
|
||||
|
||||
const tpl_navigation = (o) => html`
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link active" id="about-tab" href="#about-tabpanel" aria-controls="about-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>${i18n_about}</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link" id="commands-tab" href="#commands-tabpanel" aria-controls="commands-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>${i18n_commands}</a>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
|
||||
export default (o) => html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="changeStatusModalLabel">${modal_title}</h5>
|
||||
<h5 class="modal-title" id="converse-modtools-modal-label">${i18n_modal_title}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<span class="modal-alert"></span>
|
||||
<div class="container brand-heading-container">
|
||||
<h6 class="brand-heading">Converse</h6>
|
||||
<p class="brand-subtitle">${o.version_name}</p>
|
||||
<p class="brand-subtitle">${unsafeHTML(xss.filterXSS(first_subtitle, {'whiteList': {'a': []}}))}</p>
|
||||
<p class="brand-subtitle">${unsafeHTML(xss.filterXSS(second_subtitle, {'whiteList': {'a': []}}))}</p>
|
||||
${ tpl_navigation(o) }
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane tab-pane--columns active" id="about-tabpanel" role="tabpanel" aria-labelledby="about-tab">
|
||||
<span class="modal-alert"></span>
|
||||
<br/>
|
||||
<div class="container brand-heading-container">
|
||||
<h6 class="brand-heading">Converse</h6>
|
||||
<p class="brand-subtitle">${o.version_name}</p>
|
||||
<p class="brand-subtitle">${unsafeHTML(xss.filterXSS(first_subtitle, {'whiteList': {'a': []}}))}</p>
|
||||
<p class="brand-subtitle">${unsafeHTML(xss.filterXSS(second_subtitle, {'whiteList': {'a': []}}))}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane tab-pane--columns" id="commands-tabpanel" role="tabpanel" aria-labelledby="commands-tab">
|
||||
<converse-adhoc-commands/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,41 +0,0 @@
|
||||
import { __ } from '@converse/headless/i18n';
|
||||
import { html } from "lit-html";
|
||||
import { modal_close_button, modal_header_close_button } from "./buttons"
|
||||
import { repeat } from 'lit-html/directives/repeat.js';
|
||||
|
||||
|
||||
const i18n_commands_found = __('Commands found');
|
||||
const i18n_no_commands_found = __('No commands found');
|
||||
|
||||
|
||||
const tpl_command = (o, command) => html`
|
||||
<li class="room-item list-group-item">
|
||||
<div class="available-chatroom d-flex flex-row">
|
||||
<a class="open-room available-room w-100"
|
||||
@click=${o.openRoom}
|
||||
data-command-node="${command.node}"
|
||||
data-command-jid="${command.jid}"
|
||||
data-command-name="${command.name}"
|
||||
title="${command.name}"
|
||||
href="#">${command.name || command.jid}</a>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
|
||||
export default (o) => html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="room-details-modal-label">${o.display_name}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active">${ o.commands.length ? i18n_commands_found : i18n_no_commands_found }:</li>
|
||||
${repeat(o.commands, item => item.jid, item => tpl_command(o, item))}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">${modal_close_button}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -14,7 +14,7 @@ export default (o) => html`
|
||||
<canvas class="avatar align-self-center" height="40" width="40"></canvas>
|
||||
</a>
|
||||
<span class="username w-100 align-self-center">${o.fullname}</span>
|
||||
${o._converse.api.settings.get('show_client_info') ? html`<a class="controlbox-heading__btn show-client-info fa fa-info-circle align-self-center" title="${i18n_details}"></a>` : ''}
|
||||
${o._converse.api.settings.get('show_client_info') ? html`<a class="controlbox-heading__btn show-client-info fa fa-cog align-self-center" title="${i18n_details}" @click=${o.showUserSettingsModal}></a>` : ''}
|
||||
${o._converse.api.settings.get('allow_logout') ? html`<a class="controlbox-heading__btn logout fa fa-sign-out-alt align-self-center" title="${i18n_logout}"></a>` : ''}
|
||||
</div>
|
||||
<div class="d-flex xmpp-status">
|
||||
|
@ -26,7 +26,7 @@ const URL_REGEX = /\b(https?\:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
|
||||
function getAutoCompleteProperty (name, options) {
|
||||
return {
|
||||
'muc#roomconfig_lang': 'language',
|
||||
'muc#roomconfig_roomsecret': options.new_password ? 'new-password' : 'current-password'
|
||||
'muc#roomconfig_roomsecret': options?.new_password ? 'new-password' : 'current-password'
|
||||
}[name];
|
||||
}
|
||||
|
||||
@ -660,7 +660,7 @@ u.xForm2webForm = function (field, stanza, options) {
|
||||
'id': u.getUniqueId(),
|
||||
'label': field.getAttribute('label') || '',
|
||||
'name': name,
|
||||
'fixed_username': options.fixed_username,
|
||||
'fixed_username': options?.fixed_username,
|
||||
'autocomplete': getAutoCompleteProperty(name, options),
|
||||
'placeholder': null,
|
||||
'required': !!field.querySelector('required'),
|
||||
|
@ -28,7 +28,6 @@
|
||||
enable_smacks: true,
|
||||
i18n: 'en',
|
||||
message_archiving: 'always',
|
||||
persistent_store: 'IndexedDB',
|
||||
muc_domain: 'conference.chat.example.org',
|
||||
muc_respect_autojoin: true,
|
||||
view_mode: 'fullscreen',
|
||||
|
Loading…
Reference in New Issue
Block a user