${this.model.get('moderation_reason')}` : '' }\n `;\n }\n\n renderMessageText () {\n const i18n_edited = __('This message has been edited');\n const i18n_show = __('Show more');\n const is_groupchat_message = (this.model.get('type') === 'groupchat');\n const i18n_show_less = __('Show less');\n\n const tpl_spoiler_hint = html`\n
${ o.status }
` : '' }\n `;\n}\n","import { __ } from 'i18n';\nimport { _converse } from \"@converse/headless/core\";\nimport { html } from 'lit';\n\n\nexport async function getHeadingDropdownItem (promise_or_data) {\n const data = await promise_or_data;\n return html`\n ${data.i18n_text}\n `;\n}\n\nexport async function getHeadingStandaloneButton (promise_or_data) {\n const data = await promise_or_data;\n return html`\n \n `;\n}\n\nexport async function clearMessages (chat) {\n const result = confirm(__('Are you sure you want to clear the messages from this conversation?'));\n if (result === true) {\n await chat.clearMessages();\n }\n}\n\n\nexport function parseMessageForCommands (chat, text) {\n const match = text.replace(/^\\s*/, '').match(/^\\/(.*)\\s*$/);\n if (match) {\n if (match[1] === 'clear') {\n clearMessages(chat);\n return true;\n } else if (match[1] === 'close') {\n _converse.chatboxviews.get(chat.get('jid'))?.close();\n return true;\n } else if (match[1] === 'help') {\n chat.set({ 'show_help_messages': false }, { 'silent': true });\n chat.set({ 'show_help_messages': true });\n return true;\n }\n }\n}\n\nexport function resetElementHeight (ev) {\n if (ev.target.value) {\n const height = ev.target.scrollHeight + 'px';\n if (ev.target.style.height != height) {\n ev.target.style.height = 'auto';\n ev.target.style.height = height;\n }\n } else {\n ev.target.style = '';\n }\n}\n","import UserDetailsModal from 'modals/user-details.js';\nimport debounce from 'lodash-es/debounce';\nimport tpl_chatbox_head from './templates/chat-head.js';\nimport { ElementView } from '@converse/skeletor/src/element.js';\nimport { __ } from 'i18n';\nimport { _converse, api } from \"@converse/headless/core\";\nimport { getHeadingDropdownItem, getHeadingStandaloneButton } from 'plugins/chatview/utils.js';\nimport { render } from 'lit';\n\nimport './styles//chat-head.scss';\n\n\nexport default class ChatHeading extends ElementView {\n\n async render () {\n const tpl = await this.generateHeadingTemplate();\n render(tpl, this);\n }\n\n connectedCallback () {\n super.connectedCallback();\n this.model = _converse.chatboxes.get(this.getAttribute('jid'));\n this.debouncedRender = debounce(this.render, 100);\n this.listenTo(this.model, 'vcard:change', this.debouncedRender);\n if (this.model.contact) {\n this.listenTo(this.model.contact, 'destroy', this.debouncedRender);\n }\n this.model.rosterContactAdded?.then(() => {\n this.listenTo(this.model.contact, 'change:nickname', this.debouncedRender);\n this.debouncedRender();\n });\n this.render();\n }\n\n showUserDetailsModal (ev) {\n ev.preventDefault();\n api.modal.show(UserDetailsModal, { model: this.model }, ev);\n }\n\n close () {\n _converse.chatboxviews.get(this.getAttribute('jid'))?.close();\n }\n\n /**\n * Returns a list of objects which represent buttons for the chat's header.\n * @async\n * @emits _converse#getHeadingButtons\n */\n getHeadingButtons () {\n const buttons = [\n {\n 'a_class': 'show-user-details-modal',\n 'handler': ev => this.showUserDetailsModal(ev),\n 'i18n_text': __('Details'),\n 'i18n_title': __('See more information about this person'),\n 'icon_class': 'fa-id-card',\n 'name': 'details',\n 'standalone': api.settings.get('view_mode') === 'overlayed'\n }\n ];\n if (!api.settings.get('singleton')) {\n buttons.push({\n 'a_class': 'close-chatbox-button',\n 'handler': ev => this.close(ev),\n 'i18n_text': __('Close'),\n 'i18n_title': __('Close and end this conversation'),\n 'icon_class': 'fa-times',\n 'name': 'close',\n 'standalone': api.settings.get('view_mode') === 'overlayed'\n });\n }\n /**\n * *Hook* which allows plugins to add more buttons to a chat's heading.\n * @event _converse#getHeadingButtons\n * @example\n * api.listen.on('getHeadingButtons', (view, buttons) => {\n * buttons.push({\n * 'i18n_title': __('Foo'),\n * 'i18n_text': __('Foo Bar'),\n * 'handler': ev => alert('Foo!'),\n * 'a_class': 'toggle-foo',\n * 'icon_class': 'fa-foo',\n * 'name': 'foo'\n * });\n * return buttons;\n * });\n */\n const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));\n if (chatview) {\n return _converse.api.hook('getHeadingButtons', chatview, buttons);\n } else {\n return buttons; // Happens during tests\n }\n }\n\n async generateHeadingTemplate () {\n const vcard = this.model?.vcard;\n const vcard_json = vcard ? vcard.toJSON() : {};\n const i18n_profile = __(\"The User's Profile Image\");\n const avatar_data = Object.assign(\n {\n 'alt_text': i18n_profile,\n 'extra_classes': '',\n 'height': 40,\n 'width': 40\n },\n vcard_json\n );\n const heading_btns = await this.getHeadingButtons();\n const standalone_btns = heading_btns.filter(b => b.standalone);\n const dropdown_btns = heading_btns.filter(b => !b.standalone);\n return tpl_chatbox_head(\n Object.assign(this.model.toJSON(), {\n avatar_data,\n 'display_name': this.model.getDisplayName(),\n 'dropdown_btns': dropdown_btns.map(b => getHeadingDropdownItem(b)),\n 'showUserDetailsModal': ev => this.showUserDetailsModal(ev),\n 'standalone_btns': standalone_btns.map(b => getHeadingStandaloneButton(b))\n })\n );\n }\n\n\n}\n\napi.elements.define('converse-chat-heading', ChatHeading);\n","import { __ } from 'i18n';\nimport { api } from \"@converse/headless/core\";\nimport { html } from \"lit\";\nimport { resetElementHeight } from '../utils.js';\n\n\nexport default (o) => {\n const label_message = o.composing_spoiler ? __('Hidden message') : __('Message');\n const label_spoiler_hint = __('Optional hint');\n const show_send_button = api.settings.get('show_send_button');\n\n return html`\n `;\n}\n","import tpl_message_form from './templates/message-form.js';\nimport { ElementView } from '@converse/skeletor/src/element.js';\nimport { __ } from 'i18n';\nimport { _converse, api, converse } from \"@converse/headless/core\";\nimport { parseMessageForCommands } from './utils.js';\n\nconst { u } = converse.env;\n\n\nexport default class MessageForm extends ElementView {\n\n async connectedCallback () {\n super.connectedCallback();\n this.model = _converse.chatboxes.get(this.getAttribute('jid'));\n await this.model.initialized;\n this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);\n this.render();\n }\n\n toHTML () {\n return tpl_message_form(\n Object.assign(this.model.toJSON(), {\n 'onDrop': ev => this.onDrop(ev),\n 'hint_value': this.querySelector('.spoiler-hint')?.value,\n 'message_value': this.querySelector('.chat-textarea')?.value,\n 'onChange': ev => this.model.set({'draft': ev.target.value}),\n 'onKeyDown': ev => this.onKeyDown(ev),\n 'onKeyUp': ev => this.onKeyUp(ev),\n 'onPaste': ev => this.onPaste(ev),\n 'viewUnreadMessages': ev => this.viewUnreadMessages(ev)\n })\n );\n }\n\n /**\n * Insert a particular string value into the textarea of this chat box.\n * @param {string} value - The value to be inserted.\n * @param {(boolean|string)} [replace] - Whether an existing value\n * should be replaced. If set to `true`, the entire textarea will\n * be replaced with the new value. If set to a string, then only\n * that string will be replaced *if* a position is also specified.\n * @param {integer} [position] - The end index of the string to be\n * replaced with the new value.\n */\n insertIntoTextArea (value, replace = false, correcting = false, position) {\n const textarea = this.querySelector('.chat-textarea');\n if (correcting) {\n u.addClass('correcting', textarea);\n } else {\n u.removeClass('correcting', textarea);\n }\n if (replace) {\n if (position && typeof replace == 'string') {\n textarea.value = textarea.value.replace(new RegExp(replace, 'g'), (match, offset) =>\n offset == position - replace.length ? value + ' ' : match\n );\n } else {\n textarea.value = value;\n }\n } else {\n let existing = textarea.value;\n if (existing && existing[existing.length - 1] !== ' ') {\n existing = existing + ' ';\n }\n textarea.value = existing + value + ' ';\n }\n const ev = document.createEvent('HTMLEvents');\n ev.initEvent('change', false, true);\n textarea.dispatchEvent(ev);\n u.placeCaretAtEnd(textarea);\n }\n\n onMessageCorrecting (message) {\n if (message.get('correcting')) {\n this.insertIntoTextArea(u.prefixMentions(message), true, true);\n } else {\n const currently_correcting = this.model.messages.findWhere('correcting');\n if (currently_correcting && currently_correcting !== message) {\n this.insertIntoTextArea(u.prefixMentions(message), true, true);\n } else {\n this.insertIntoTextArea('', true, false);\n }\n }\n }\n\n onEscapePressed (ev) {\n ev.preventDefault();\n const idx = this.model.messages.findLastIndex('correcting');\n const message = idx >= 0 ? this.model.messages.at(idx) : null;\n if (message) {\n message.save('correcting', false);\n }\n this.insertIntoTextArea('', true, false);\n }\n\n onPaste (ev) {\n ev.stopPropagation();\n if (ev.clipboardData.files.length !== 0) {\n ev.preventDefault();\n // Workaround for quirk in at least Firefox 60.7 ESR:\n // It seems that pasted files disappear from the event payload after\n // the event has finished, which apparently happens during async\n // processing in sendFiles(). So we copy the array here.\n this.model.sendFiles(Array.from(ev.clipboardData.files));\n return;\n }\n this.model.set({'draft': ev.clipboardData.getData('text/plain')});\n }\n\n onKeyUp (ev) {\n this.model.set({'draft': ev.target.value});\n }\n\n onKeyDown (ev) {\n if (ev.ctrlKey) {\n // When ctrl is pressed, no chars are entered into the textarea.\n return;\n }\n if (!ev.shiftKey && !ev.altKey && !ev.metaKey) {\n if (ev.keyCode === converse.keycodes.TAB) {\n const value = u.getCurrentWord(ev.target, null, /(:.*?:)/g);\n if (value.startsWith(':')) {\n ev.preventDefault();\n ev.stopPropagation();\n this.model.trigger('emoji-picker-autocomplete', ev.target, value);\n }\n } else if (ev.keyCode === converse.keycodes.FORWARD_SLASH) {\n // Forward slash is used to run commands. Nothing to do here.\n return;\n } else if (ev.keyCode === converse.keycodes.ESCAPE) {\n return this.onEscapePressed(ev, this);\n } else if (ev.keyCode === converse.keycodes.ENTER) {\n return this.onFormSubmitted(ev);\n } else if (ev.keyCode === converse.keycodes.UP_ARROW && !ev.target.selectionEnd) {\n const textarea = this.querySelector('.chat-textarea');\n if (!textarea.value || u.hasClass('correcting', textarea)) {\n return this.model.editEarlierMessage();\n }\n } else if (\n ev.keyCode === converse.keycodes.DOWN_ARROW &&\n ev.target.selectionEnd === ev.target.value.length &&\n u.hasClass('correcting', this.querySelector('.chat-textarea'))\n ) {\n return this.model.editLaterMessage();\n }\n }\n if (\n [\n converse.keycodes.SHIFT,\n converse.keycodes.META,\n converse.keycodes.META_RIGHT,\n converse.keycodes.ESCAPE,\n converse.keycodes.ALT\n ].includes(ev.keyCode)\n ) {\n return;\n }\n if (this.model.get('chat_state') !== _converse.COMPOSING) {\n // Set chat state to composing if keyCode is not a forward-slash\n // (which would imply an internal command and not a message).\n this.model.setChatState(_converse.COMPOSING);\n }\n }\n\n parseMessageForCommands (text) {\n // Wrap util so that we can override in the MUC message-form component\n return parseMessageForCommands(this.model, text);\n }\n\n async onFormSubmitted (ev) {\n ev?.preventDefault?.();\n\n const textarea = this.querySelector('.chat-textarea');\n const message_text = textarea.value.trim();\n if (\n (api.settings.get('message_limit') && message_text.length > api.settings.get('message_limit')) ||\n !message_text.replace(/\\s/g, '').length\n ) {\n return;\n }\n if (!_converse.connection.authenticated) {\n const err_msg = __('Sorry, the connection has been lost, and your message could not be sent');\n api.alert('error', __('Error'), err_msg);\n api.connection.reconnect();\n return;\n }\n let spoiler_hint,\n hint_el = {};\n if (this.model.get('composing_spoiler')) {\n hint_el = this.querySelector('form.sendXMPPMessage input.spoiler-hint');\n spoiler_hint = hint_el.value;\n }\n u.addClass('disabled', textarea);\n textarea.setAttribute('disabled', 'disabled');\n this.querySelector('converse-emoji-dropdown')?.hideMenu();\n\n const is_command = this.parseMessageForCommands(message_text);\n const message = is_command ? null : await this.model.sendMessage(message_text, spoiler_hint);\n if (is_command || message) {\n hint_el.value = '';\n textarea.value = '';\n u.removeClass('correcting', textarea);\n textarea.style.height = 'auto';\n this.model.set({'draft': ''});\n }\n if (api.settings.get('view_mode') === 'overlayed') {\n // XXX: Chrome flexbug workaround. The .chat-content area\n // doesn't resize when the textarea is resized to its original size.\n const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));\n const msgs_container = chatview.querySelector('.chat-content__messages');\n msgs_container.parentElement.style.display = 'none';\n }\n textarea.removeAttribute('disabled');\n u.removeClass('disabled', textarea);\n\n if (api.settings.get('view_mode') === 'overlayed') {\n // XXX: Chrome flexbug workaround.\n const chatview = _converse.chatboxviews.get(this.getAttribute('jid'));\n const msgs_container = chatview.querySelector('.chat-content__messages');\n msgs_container.parentElement.style.display = '';\n }\n // Suppress events, otherwise superfluous CSN gets set\n // immediately after the message, causing rate-limiting issues.\n this.model.setChatState(_converse.ACTIVE, { 'silent': true });\n textarea.focus();\n }\n}\n\napi.elements.define('converse-message-form', MessageForm);\n","import { __ } from 'i18n';\nimport { api } from '@converse/headless/core';\nimport { html } from 'lit';\n\n\nexport default (o) => {\n const unread_msgs = __('You have unread messages');\n const message_limit = api.settings.get('message_limit');\n const show_call_button = api.settings.get('visible_toolbar_buttons').call;\n const show_emoji_button = api.settings.get('visible_toolbar_buttons').emoji;\n const show_send_button = api.settings.get('show_send_button');\n const show_spoiler_button = api.settings.get('visible_toolbar_buttons').spoiler;\n const show_toolbar = api.settings.get('show_toolbar');\n return html`\n ${ o.model.get('scrolled') && o.model.get('num_unread') ?\n html`${_converse.VERSION_NAME}
\n\n Open Source XMPP chat client\n brought to you by Opkode\n
\n\n Translate\n it into your own language\n
\n `\n : ''}\n `;\n }\n}\n\napi.elements.define('converse-brand-byline', ConverseBrandByline);\n","import { api } from '@converse/headless/core';\nimport { CustomElement } from './element.js';\nimport { html } from 'lit';\n\n\nexport class ConverseBrandLogo extends CustomElement {\n\n render () { // eslint-disable-line class-methods-use-this\n const is_fullscreen = api.settings.get('view_mode') === 'fullscreen';\n return html`\n \n \n \n \n converse.js\n ${is_fullscreen\n ? html`\n \n `\n : ''}\n \n \n \n `;\n }\n}\n\napi.elements.define('converse-brand-logo', ConverseBrandLogo);\n","import './brand-byline.js';\nimport './brand-logo.js';\nimport { CustomElement } from './element.js';\nimport { api } from '@converse/headless/core';\nimport { html } from 'lit-html';\n\n\nexport class ConverseBrandHeading extends CustomElement {\n\n render () { // eslint-disable-line class-methods-use-this\n return html`\n${i18n_disconnected}
` : '' }\n `;\n}\n\n\nexport default (o) => html`\n${ o.status }
` : '' }\n `;\n}\n","import ChatHeading from 'plugins/chatview/heading.js';\nimport tpl_chat_head from './templates/chat-head.js';\nimport { __ } from 'i18n';\nimport { _converse, api } from \"@converse/headless/core\";\nimport { getHeadingDropdownItem, getHeadingStandaloneButton } from 'plugins/chatview/utils.js';\n\n\nexport default class HeadlinesHeading extends ChatHeading {\n\n async connectedCallback () {\n super.connectedCallback();\n this.model = _converse.chatboxes.get(this.getAttribute('jid'));\n await this.model.initialized;\n this.render();\n }\n\n async generateHeadingTemplate () {\n const heading_btns = await this.getHeadingButtons();\n const standalone_btns = heading_btns.filter(b => b.standalone);\n const dropdown_btns = heading_btns.filter(b => !b.standalone);\n return tpl_chat_head(\n Object.assign(this.model.toJSON(), {\n 'display_name': this.model.getDisplayName(),\n 'dropdown_btns': dropdown_btns.map(b => getHeadingDropdownItem(b)),\n 'standalone_btns': standalone_btns.map(b => getHeadingStandaloneButton(b))\n })\n );\n }\n\n /**\n * Returns a list of objects which represent buttons for the headlines header.\n * @async\n * @emits _converse#getHeadingButtons\n * @method HeadlinesHeading#getHeadingButtons\n */\n getHeadingButtons () {\n const buttons = [];\n if (!api.settings.get('singleton')) {\n buttons.push({\n 'a_class': 'close-chatbox-button',\n 'handler': ev => this.close(ev),\n 'i18n_text': __('Close'),\n 'i18n_title': __('Close these announcements'),\n 'icon_class': 'fa-times',\n 'name': 'close',\n 'standalone': api.settings.get('view_mode') === 'overlayed'\n });\n }\n return _converse.api.hook('getHeadingButtons', this, buttons);\n }\n\n}\n\napi.elements.define('converse-headlines-heading', HeadlinesHeading);\n","import '../heading.js';\nimport { html } from \"lit\";\n\nexport default (model) => html`\n${i18n_moved}
\n\n o.onSwitch(ev)}>${o.moved_jid}\n
`;\n}\n\nexport default (o) => {\n const i18n_non_existent = __('This groupchat no longer exists');\n const i18n_reason = __('The following reason was given: \"%1$s\"', o.reason || '');\n return html`\n${i18n_reason}
` : '' }\n ${ o.moved_jid ? tpl_moved(o) : '' }\n `;\n}\n","import tpl_muc_destroyed from './templates/muc-destroyed.js';\nimport { CustomElement } from 'shared/components/element';\nimport { _converse, api } from \"@converse/headless/core\";\n\n\nclass MUCDestroyed extends CustomElement {\n\n static get properties () {\n return {\n 'jid': { type: String }\n }\n }\n\n connectedCallback () {\n super.connectedCallback();\n this.model = _converse.chatboxes.get(this.jid);\n }\n\n render () {\n const reason = this.model.get('destroyed_reason');\n const moved_jid = this.model.get('moved_jid');\n return tpl_muc_destroyed({\n moved_jid,\n reason,\n 'onSwitch': ev => this.onSwitch(ev)\n });\n }\n\n async onSwitch (ev) {\n ev.preventDefault();\n const moved_jid = this.model.get('moved_jid');\n const room = await api.rooms.get(moved_jid, {}, true);\n room.maybeShow(true);\n this.model.destroy();\n }\n}\n\napi.elements.define('converse-muc-destroyed', MUCDestroyed);\n","import { html } from \"lit\";\n\n\nexport default (messages) => {\n return html`\n${m}
`) }\n${i18n_topic}: ${unsafeHTML(xss.filterXSS(o.subject.text, {'whiteList': {}}))}
\n${i18n_topic_author}: ${o.subject && o.subject.author}
\n `;\n}\n\n\nexport default (o) => {\n const i18n_address = __('Groupchat address (JID)');\n const i18n_archiving = __('Message archiving');\n const i18n_archiving_help = __('Messages are archived on the server');\n const i18n_desc = __('Description');\n const i18n_features = __('Features');\n const i18n_hidden = __('Hidden');\n const i18n_hidden_help = __('This groupchat is not publicly searchable');\n const i18n_members_help = __('This groupchat is restricted to members only');\n const i18n_members_only = __('Members only');\n const i18n_moderated = __('Moderated');\n const i18n_moderated_help = __('Participants entering this groupchat need to request permission to write');\n const i18n_name = __('Name');\n const i18n_no_pass_help = __('This groupchat does not require a password upon entry');\n const i18n_no_password_required = __('No password required');\n const i18n_not_anonymous = __('Not anonymous');\n const i18n_not_anonymous_help = __('All other groupchat participants can see your XMPP address');\n const i18n_not_moderated = __('Not moderated');\n const i18n_not_moderated_help = __('Participants entering this groupchat can write right away');\n const i18n_online_users = __('Online users');\n const i18n_open = __('Open');\n const i18n_open_help = __('Anyone can join this groupchat');\n const i18n_password_help = __('This groupchat requires a password before entry');\n const i18n_password_protected = __('Password protected');\n const i18n_persistent = __('Persistent');\n const i18n_persistent_help = __('This groupchat persists even if it\\'s unoccupied');\n const i18n_public = __('Public');\n const i18n_semi_anon = __('Semi-anonymous');\n const i18n_semi_anon_help = __('Only moderators can see your XMPP address');\n const i18n_temporary = __('Temporary');\n const i18n_temporary_help = __('This groupchat will disappear once the last person leaves');\n return html`\n\n
\n ${i18n_providers}\n ${i18n_providers_link}.\n
\n `;\n};\n\nconst tpl_fetch_form_buttons = () => {\n const i18n_register = __('Fetch registration form');\n const i18n_existing_account = __('Already have a chat account?');\n const i18n_login = __('Log in here');\n return html`\n \n${i18n_existing_account}
\n \n'+message+'
'\n );\n flash.classList.remove('hidden');\n }\n\n /**\n * Report back to the user any error messages received from the\n * XMPP server after attempted registration.\n * @private\n * @method _converse.RegisterPanel#reportErrors\n * @param { XMLElement } stanza - The IQ stanza received from the XMPP server\n */\n reportErrors (stanza) {\n const errors = stanza.querySelectorAll('error');\n errors.forEach(e => this.showValidationError(e.textContent));\n if (!errors.length) {\n const message = __('The provider rejected your registration attempt. '+\n 'Please check the values you entered for correctness.');\n this.showValidationError(message);\n }\n }\n\n renderProviderChoiceForm (ev) {\n if (ev && ev.preventDefault) { ev.preventDefault(); }\n _converse.connection._proto._abortAllRequests();\n _converse.connection.reset();\n this.render();\n }\n\n abortRegistration () {\n _converse.connection._proto._abortAllRequests();\n _converse.connection.reset();\n if ([FETCHING_FORM, REGISTRATION_FORM].includes(this.model.get('registration_status'))) {\n if (api.settings.get('registration_domain')) {\n this.fetchRegistrationForm(api.settings.get('registration_domain'));\n }\n } else {\n this.render();\n }\n }\n\n /**\n * Handler, when the user submits the registration form.\n * Provides form error feedback or starts the registration process.\n * @private\n * @method _converse.RegisterPanel#submitRegistrationForm\n * @param { HTMLElement } form - The HTML form that was submitted\n */\n submitRegistrationForm (form) {\n const has_empty_inputs = Array.from(this.querySelectorAll('input.required'))\n .reduce((result, input) => {\n if (input.value === '') {\n input.classList.add('error');\n return result + 1;\n }\n return result;\n }, 0);\n if (has_empty_inputs) { return; }\n\n const inputs = sizzle(':input:not([type=button]):not([type=submit])', form);\n const iq = $iq({'type': 'set', 'id': u.getUniqueId()})\n .c(\"query\", {xmlns:Strophe.NS.REGISTER});\n\n if (this.form_type === 'xform') {\n iq.c(\"x\", {xmlns: Strophe.NS.XFORM, type: 'submit'});\n\n const xml_nodes = inputs.map(i => utils.webForm2xForm(i)).filter(n => n);\n xml_nodes.forEach(n => iq.cnode(n).up());\n } else {\n inputs.forEach(input => iq.c(input.getAttribute('name'), {}, input.value));\n }\n _converse.connection._addSysHandler(this._onRegisterIQ.bind(this), null, \"iq\", null, null);\n _converse.connection.send(iq);\n this.setFields(iq.tree());\n }\n\n /* Stores the values that will be sent to the XMPP server during attempted registration.\n * @private\n * @method _converse.RegisterPanel#setFields\n * @param { XMLElement } stanza - the IQ stanza that will be sent to the XMPP server.\n */\n setFields (stanza) {\n const query = stanza.querySelector('query');\n const xform = sizzle(`x[xmlns=\"${Strophe.NS.XFORM}\"]`, query);\n if (xform.length > 0) {\n this._setFieldsFromXForm(xform.pop());\n } else {\n this._setFieldsFromLegacy(query);\n }\n }\n\n _setFieldsFromLegacy (query) {\n [].forEach.call(query.children, field => {\n if (field.tagName.toLowerCase() === 'instructions') {\n this.instructions = Strophe.getText(field);\n return;\n } else if (field.tagName.toLowerCase() === 'x') {\n if (field.getAttribute('xmlns') === 'jabber:x:oob') {\n this.urls.concat(sizzle('url', field).map(u => u.textContent));\n }\n return;\n }\n this.fields[field.tagName.toLowerCase()] = Strophe.getText(field);\n });\n this.form_type = 'legacy';\n }\n\n _setFieldsFromXForm (xform) {\n this.title = xform.querySelector('title')?.textContent;\n this.instructions = xform.querySelector('instructions')?.textContent;\n xform.querySelectorAll('field').forEach(field => {\n const _var = field.getAttribute('var');\n if (_var) {\n this.fields[_var.toLowerCase()] = field.querySelector('value')?.textContent ?? '';\n } else {\n // TODO: other option seems to be type=\"fixed\"\n log.warn(\"Found field we couldn't parse\");\n }\n });\n this.form_type = 'xform';\n }\n\n /**\n * Callback method that gets called when a return IQ stanza\n * is received from the XMPP server, after attempting to\n * register a new user.\n * @private\n * @method _converse.RegisterPanel#reportErrors\n * @param { XMLElement } stanza - The IQ stanza.\n */\n _onRegisterIQ (stanza) {\n if (stanza.getAttribute(\"type\") === \"error\") {\n log.error(\"Registration failed.\");\n this.reportErrors(stanza);\n\n let error = stanza.getElementsByTagName(\"error\");\n if (error.length !== 1) {\n _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, \"unknown\");\n return false;\n }\n error = error[0].firstElementChild.tagName.toLowerCase();\n if (error === 'conflict') {\n _converse.connection._changeConnectStatus(Strophe.Status.CONFLICT, error);\n } else if (error === 'not-acceptable') {\n _converse.connection._changeConnectStatus(Strophe.Status.NOTACCEPTABLE, error);\n } else {\n _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error);\n }\n } else {\n _converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null);\n }\n return false;\n }\n}\n\napi.elements.define('converse-register-panel', RegisterPanel);\n","/**\n * @module converse-register\n * @description\n * This is a Converse.js plugin which add support for in-band registration\n * as specified in XEP-0077.\n * @copyright 2020, the Converse.js contributors\n * @license Mozilla Public License (MPLv2)\n */\nimport './panel.js';\nimport '../controlbox/index.js';\nimport { __ } from 'i18n';\nimport { _converse, api, converse } from '@converse/headless/core';\n\n// Strophe methods for building stanzas\nconst { Strophe } = converse.env;\n\n// Add Strophe Namespaces\nStrophe.addNamespace('REGISTER', 'jabber:iq:register');\n\n// Add Strophe Statuses\nconst i = Object.keys(Strophe.Status).reduce((max, k) => Math.max(max, Strophe.Status[k]), 0);\nStrophe.Status.REGIFAIL = i + 1;\nStrophe.Status.REGISTERED = i + 2;\nStrophe.Status.CONFLICT = i + 3;\nStrophe.Status.NOTACCEPTABLE = i + 5;\n\nconverse.plugins.add('converse-register', {\n\n dependencies: ['converse-controlbox'],\n\n enabled () {\n return true;\n },\n\n initialize () {\n _converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';\n _converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';\n _converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';\n _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';\n\n api.settings.extend({\n 'allow_registration': true,\n 'domain_placeholder': __(' e.g. conversejs.org'), // Placeholder text shown in the domain input on the registration form\n 'providers_link': 'https://compliance.conversations.im/', // Link to XMPP providers shown on registration page\n 'registration_domain': ''\n });\n\n async function setActiveForm (value) {\n await api.waitUntil('controlBoxInitialized');\n const controlbox = _converse.chatboxes.get('controlbox');\n controlbox.set({ 'active-form': value });\n }\n _converse.router.route('converse/login', () => setActiveForm('login'));\n _converse.router.route('converse/register', () => setActiveForm('register'));\n\n\n api.listen.on('controlBoxInitialized', view => {\n view.model.on('change:active-form', view.showLoginOrRegisterForm, view);\n });\n }\n});\n","import xss from \"xss/dist/xss\";\nimport { __ } from 'i18n';\nimport { html } from \"lit\";\nimport { modal_header_close_button } from \"modals/templates/buttons.js\"\nimport { unsafeHTML } from \"lit/directives/unsafe-html.js\";\n\n\nconst nickname_input = (o) => {\n const i18n_nickname = __('Nickname');\n const i18n_required_field = __('This field is required');\n return html`\n${i18n_jid} ${o.jid}
\n${i18n_desc} ${o.desc}
\n${i18n_occ} ${o.occ}
\n${i18n_features}\n