diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index eacff43af5..6a7a2b4950 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -1718,6 +1718,29 @@ providers_link The hyperlink on the registration form which points to a directory of public XMPP servers. +registration_providers +---------------------- + +* Default: ``[]`` + +An optional array of XMPP provider domains to suggest via autocomplete when the user enters the +provider on the registration form. + +For example: + +.. code-block:: javascript + + converse.initialize({ + registration_providers: [ + 'conversejs.org', + 'jabber.org', + 'xmpp.jp', + 'trashserver.net', + ] + }); + +Suggestions are shown via the ```` component and filtered by prefix. + .. _`assets_path`: assets_path diff --git a/src/plugins/chatview/index.js b/src/plugins/chatview/index.js index a9fa389d0d..5cc74822c9 100644 --- a/src/plugins/chatview/index.js +++ b/src/plugins/chatview/index.js @@ -54,7 +54,8 @@ converse.plugins.add('converse-chatview', { 'call': false, 'clear': true, 'emoji': true, - 'spoiler': false + 'spoiler': false, + 'location': true } }); diff --git a/src/plugins/chatview/templates/message-form.js b/src/plugins/chatview/templates/message-form.js index b5bbcfe41b..cca4624daa 100644 --- a/src/plugins/chatview/templates/message-form.js +++ b/src/plugins/chatview/templates/message-form.js @@ -15,6 +15,7 @@ export default (el) => { const show_emoji_button = api.settings.get("visible_toolbar_buttons").emoji; const show_send_button = api.settings.get("show_send_button"); const show_spoiler_button = api.settings.get("visible_toolbar_buttons").spoiler; + const show_location_button = api.settings.get("visible_toolbar_buttons").location; const show_toolbar = api.settings.get("show_toolbar"); return html`
{ ?show_emoji_button="${show_emoji_button}" ?show_send_button="${show_send_button}" ?show_spoiler_button="${show_spoiler_button}" + ?show_location_button="${show_location_button}" ?show_toolbar="${show_toolbar}" message_limit="${message_limit}" >` diff --git a/src/plugins/muc-views/templates/message-form.js b/src/plugins/muc-views/templates/message-form.js index e485d7bafe..2cdf46f4a0 100644 --- a/src/plugins/muc-views/templates/message-form.js +++ b/src/plugins/muc-views/templates/message-form.js @@ -15,6 +15,7 @@ export default (el) => { const show_emoji_button = api.settings.get("visible_toolbar_buttons").emoji; const show_send_button = api.settings.get("show_send_button"); const show_spoiler_button = api.settings.get("visible_toolbar_buttons").spoiler; + const show_location_button = api.settings.get("visible_toolbar_buttons").location; const show_toolbar = api.settings.get("show_toolbar"); return html` @@ -30,6 +31,7 @@ export default (el) => { ?show_emoji_button="${show_emoji_button}" ?show_send_button="${show_send_button}" ?show_spoiler_button="${show_spoiler_button}" + ?show_location_button="${show_location_button}" ?show_toolbar="${show_toolbar}" message_limit="${message_limit}" >` diff --git a/src/plugins/register/index.js b/src/plugins/register/index.js index 99e90dc90d..2d9526d3e2 100644 --- a/src/plugins/register/index.js +++ b/src/plugins/register/index.js @@ -10,6 +10,7 @@ import { __ } from 'i18n'; import { routeToForm } from './utils.js'; import RegistrationForm from './form.js'; import RegisterLink from './register_link.js'; +import 'shared/autocomplete/index.js'; // Strophe methods for building stanzas const { Strophe } = converse.env; @@ -43,7 +44,10 @@ converse.plugins.add('converse-register', { allow_registration: true, domain_placeholder: __(' e.g. conversejs.org'), // Placeholder text shown in the domain input on the registration form providers_link: 'https://providers.xmpp.net/', // Link to XMPP providers shown on registration page - registration_domain: '' + registration_domain: '', + // Optional list of known public XMPP providers to suggest during registration + // e.g.: ['conversejs.org', 'jabber.org', 'xmpp.jp'] + registration_providers: [] }); const exports = { RegisterLink, RegistrationForm }; diff --git a/src/plugins/register/templates/choose_provider.js b/src/plugins/register/templates/choose_provider.js index dfdff66c52..99fe5aebb9 100644 --- a/src/plugins/register/templates/choose_provider.js +++ b/src/plugins/register/templates/choose_provider.js @@ -37,15 +37,16 @@ function tplDomainInput(el) { const i18n_providers = __('Tip: A list of public XMPP providers is available'); const i18n_providers_link = __('here'); const href_providers = api.settings.get('providers_link'); + const providers = api.settings.get('registration_providers') || []; return html` - + ?required=${true} + .value=${el.domain || ''} + >

${i18n_providers} ${i18n_providers_link}. diff --git a/src/plugins/rosterview/modals/templates/add-contact.js b/src/plugins/rosterview/modals/templates/add-contact.js index 6ff57f8e7d..ff4511c5d7 100644 --- a/src/plugins/rosterview/modals/templates/add-contact.js +++ b/src/plugins/rosterview/modals/templates/add-contact.js @@ -35,12 +35,13 @@ export default (el) => { name="jid" >` : html` `${input.slice(0, input.indexOf('@'))}@${text}`} position="below" - min_chars="2" + min_chars="1" filter="startswith" - ?required="${!api.settings.get('xhr_user_search_url')}" + triggers="@" + ?required=${!api.settings.get('xhr_user_search_url')} value="${el.state.get('jid') || ''}" placeholder="${i18n_contact_placeholder}" name="jid" diff --git a/src/plugins/rosterview/utils.js b/src/plugins/rosterview/utils.js index fa2f192a50..91dd96f36f 100644 --- a/src/plugins/rosterview/utils.js +++ b/src/plugins/rosterview/utils.js @@ -340,11 +340,14 @@ export function getGroupsAutoCompleteList() { export function getJIDsAutoCompleteList() { const roster = /** @type {RosterContacts} */ (_converse.state.roster); + const from_roster = roster.map((item) => Strophe.getDomainFromJid(item.get('jid'))); + const from_settings = api.settings.get('registration_providers') || []; return [ ...new Set([ - ...roster.map((item) => Strophe.getDomainFromJid(item.get('jid'))), + ...from_roster, _converse.session.get('domain'), - ]), + ...from_settings, + ].filter(Boolean)), ]; } diff --git a/src/shared/chat/toolbar.js b/src/shared/chat/toolbar.js index cca72707d8..ee67d307c1 100644 --- a/src/shared/chat/toolbar.js +++ b/src/shared/chat/toolbar.js @@ -24,6 +24,7 @@ export class ChatToolbar extends CustomElement { show_emoji_button: { type: Boolean }, show_send_button: { type: Boolean }, show_spoiler_button: { type: Boolean }, + show_location_button: { type: Boolean }, } } @@ -34,6 +35,7 @@ export class ChatToolbar extends CustomElement { this.hidden_occupants = false; this.show_send_button = false; this.show_spoiler_button = false; + this.show_location_button = false; this.show_call_button = false; this.show_emoji_button = false; } @@ -83,6 +85,19 @@ export class ChatToolbar extends CustomElement { buttons.push(this.getSpoilerButton()); } + if (this.show_location_button) { + const color = this.is_groupchat ? '--muc-color' : '--chat-color'; + const i18n_insert_location = __('Insert current location'); + buttons.push(html` + ` + ); + } + const domain = _converse.session.get('domain'); const http_upload_promise = api.disco.supports(Strophe.NS.HTTPUPLOAD, domain); buttons.push(html`${until(http_upload_promise.then(is_supported => this.getHTTPUploadButton(!!is_supported)),'')}`); @@ -198,6 +213,30 @@ export class ChatToolbar extends CustomElement { model: this.model }); } + + /** @param {MouseEvent} ev */ + insertLocation (ev) { + ev?.preventDefault?.(); + ev?.stopPropagation?.(); + const i18n_error = __('Unable to get current location'); + if (!('geolocation' in navigator)) { + api.toast.show('geo-not-supported', { type: 'warning', body: i18n_error }); + return; + } + navigator.geolocation.getCurrentPosition( + (pos) => { + const { latitude, longitude } = pos.coords; + const lat = latitude.toFixed(6); + const lon = longitude.toFixed(6); + const geo = `geo:${lat},${lon}`; + const draft = (this.model.get('draft') || '').trim(); + const sep = draft ? ' ' : ''; + this.model.set('draft', `${draft}${sep}${geo}`); + }, + () => api.toast.show('geo-error', { type: 'danger', body: i18n_error }), + { enableHighAccuracy: true, timeout: 8000, maximumAge: 60000 } + ); + } } api.elements.define('converse-chat-toolbar', ChatToolbar); diff --git a/src/shared/texture/templates/audio.js b/src/shared/texture/templates/audio.js index bb29a791aa..5b65ffbcf8 100644 --- a/src/shared/texture/templates/audio.js +++ b/src/shared/texture/templates/audio.js @@ -10,7 +10,8 @@ import "../styles/audio.scss"; */ export default (url, hide_url, title) => { const { hostname } = u.getURL(url); - return html`

+ const label = title || (hostname ? `Audio from ${hostname}` : 'Audio'); + return html`
${title || !hide_url ? html`
${title ? html`${title}
` : ""} @@ -19,6 +20,6 @@ export default (url, hide_url, title) => { : html`${hostname}`}
` : ""} - +
`; }; diff --git a/src/types/shared/chat/toolbar.d.ts b/src/types/shared/chat/toolbar.d.ts index ce6dc0e893..ea9a57ee77 100644 --- a/src/types/shared/chat/toolbar.d.ts +++ b/src/types/shared/chat/toolbar.d.ts @@ -24,6 +24,9 @@ export class ChatToolbar extends CustomElement { show_spoiler_button: { type: BooleanConstructor; }; + show_location_button: { + type: BooleanConstructor; + }; }; model: any; is_groupchat: any; @@ -43,6 +46,8 @@ export class ChatToolbar extends CustomElement { getSpoilerButton(): import("lit-html").TemplateResult<1>; /** @param {MouseEvent} ev */ toggleFileUpload(ev: MouseEvent): void; + /** @param {MouseEvent} ev */ + insertLocation(ev: MouseEvent): void; /** @param {InputEvent} ev */ onFileSelection(ev: InputEvent): void; /** @param {MouseEvent} ev */