Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ You can also ask questions on [StackOverflow](https://stackoverflow.com/question

Please follow the usual Github workflow. Create a fork of this repository, make your changes and then submit a pull request.

### Good first issues and bounties

If you're new, check out our docs page for beginner tasks and bounty issues:
https://conversejs.org/docs/html/good_first_issues.html

### Before submitting a pull request

Please read the [style guide](https://conversejs.org/docs/html/style_guide.html) and make sure that your code follows it.
Expand Down
23 changes: 23 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``<converse-autocomplete>`` component and filtered by prefix.

.. _`assets_path`:

assets_path
Expand Down
43 changes: 43 additions & 0 deletions docs/source/good_first_issues.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.. _good_first_issues:

===============================
Good first issues and bounties
===============================

New to Converse? Start here. This page points you to beginner‑friendly tasks and
open bounty items you can work on and get paid for.

Where to look
-------------

- Good first issues (all):
https://github.com/conversejs/converse.js/issues?q=is%3Aopen+label%3A%22good+first+issue%22
- Good first issues, ranked by upvotes:
https://github.com/conversejs/converse.js/issues?q=is%3Aopen+label%3A%22good+first+issue%22+sort%3Areactions-%2B1-desc
- Bounty issues (USD $100 per issue unless otherwise stated):
https://github.com/conversejs/converse.js/issues?q=is%3Aopen+label%3Abounty

Bounty rules (summary)
----------------------

- Anyone can work on a bounty ticket.
- Submit a quality PR which:
- Includes tests (for UI/behavioral changes, add or update existing tests).
- Updates documentation if applicable (README or docs/ where relevant).
- Passes CI and adheres to the style guide.
- Reference the issue number in the PR title/body (e.g. "addresses #1234").
- Once merged, maintainers will handle payout as per the bounty label policy.

Getting set up
--------------

- Read the development docs: :doc:`development` and :doc:`testing`.
- Follow the :doc:`style_guide`.
- Run the test suite with ``make check`` locally before opening a PR.

Tips
----

- Prefer smaller, focused PRs.
- For UX changes, include before/after screenshots or a short clip.
- If you plan a larger change, comment on the issue first to align.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Table of Contents
session
configuration
development
good_first_issues
theming
security
translations
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/chatview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ converse.plugins.add('converse-chatview', {
'call': false,
'clear': true,
'emoji': true,
'spoiler': false
'spoiler': false,
'location': true
}
});

Expand Down
2 changes: 2 additions & 0 deletions src/plugins/chatview/templates/message-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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` <form
Expand All @@ -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}"
></converse-chat-toolbar>`
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/muc-views/templates/message-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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` <form class="setNicknameButtonForm hidden">
<input type="submit" class="btn btn-primary" name="join" value="Join" />
Expand All @@ -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}"
></converse-chat-toolbar>`
Expand Down
6 changes: 5 additions & 1 deletion src/plugins/register/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 };
Expand Down
13 changes: 7 additions & 6 deletions src/plugins/register/templates/choose_provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<input
class="form-control"
required="required"
type="text"
<converse-autocomplete
.list=${providers}
filter="startswith"
name="domain"
placeholder="${domain_placeholder}"
value="${el.domain}"
/>
?required=${true}
.value=${el.domain || ''}
></converse-autocomplete>
<p class="form-text text-muted">
${i18n_providers}
<a href="${href_providers}" class="url" target="_blank" rel="noopener">${i18n_providers_link}</a>.
Expand Down
9 changes: 5 additions & 4 deletions src/plugins/rosterview/modals/templates/add-contact.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ export default (el) => {
name="jid"
></converse-autocomplete>`
: html`<converse-autocomplete
.list="${getJIDsAutoCompleteList()}"
.data="${(text, input) => `${input.slice(0, input.indexOf('@'))}@${text}`}"
.list=${getJIDsAutoCompleteList()}
.data=${(text, input) => `${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"
Expand Down
7 changes: 5 additions & 2 deletions src/plugins/rosterview/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
];
}

Expand Down
39 changes: 39 additions & 0 deletions src/shared/chat/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
}
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -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`
<button type="button"
class="btn insert-location"
@click=${this.insertLocation}
title="${i18n_insert_location}">
<converse-icon color="var(${color})" class="fa fa-location-arrow" size="1em"></converse-icon>
</button>`
);
}

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)),'')}`);
Expand Down Expand Up @@ -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);
5 changes: 3 additions & 2 deletions src/shared/texture/templates/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import "../styles/audio.scss";
*/
export default (url, hide_url, title) => {
const { hostname } = u.getURL(url);
return html`<figure class="audio-element">
const label = title || (hostname ? `Audio from ${hostname}` : 'Audio');
return html`<figure class="audio-element" role="group" aria-label="${label}">
${title || !hide_url
? html`<figcaption>
${title ? html`${title}</br>` : ""}
Expand All @@ -19,6 +20,6 @@ export default (url, hide_url, title) => {
: html`<a target="_blank" rel="noopener" title="${url}" href="${url}">${hostname}</a>`}
</figcaption>`
: ""}
<audio controls src="${url}"></audio>
<audio controls preload="metadata" src="${url}" aria-label="${label}" tabindex="0"></audio>
</figure>`;
};
5 changes: 5 additions & 0 deletions src/types/shared/chat/toolbar.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export class ChatToolbar extends CustomElement {
show_spoiler_button: {
type: BooleanConstructor;
};
show_location_button: {
type: BooleanConstructor;
};
};
model: any;
is_groupchat: any;
Expand All @@ -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 */
Expand Down