Use `repeat` directive to render roster and MUC occupant items

If we don't use `repeat`, a DOM node may be reused with different state
(e.g. the `model` it receives originally changes upon next render).

https://lit.dev/docs/templates/lists/#when-to-use-map-or-repeat

Fixes #2816
This commit is contained in:
JC Brand 2022-12-13 09:54:13 +01:00
parent 8ca265d8d5
commit e63ba2075f
6 changed files with 13 additions and 16 deletions

View File

@ -2,6 +2,7 @@
## Unreleased
- #2816 Chat highlight behaves odd
- #2925 File upload is not always enabled
- Add a "Add to Contacts" button in MUC occupant modals

View File

@ -4,7 +4,7 @@ describe("Service Discovery", function () {
describe("Whenever a server is queried for its features", function () {
fit("stores the features it receives",
it("stores the features it receives",
mock.initConverse(
['discoInitialized'], {},
async function (_converse) {

View File

@ -54,9 +54,9 @@ export default (o) => {
return html`
${o.show_form ? form(o) : '' }
<ul class="available-chatrooms list-group">
${ o.loading_items ? html`<li class="list-group-item"> ${spinner()} </li>` : '' }
${ o.loading_items ? html`<li class="list-group-item"> ${ spinner() } </li>` : '' }
${ o.feedback_text ? html`<li class="list-group-item active">${ o.feedback_text }</li>` : '' }
${repeat(o.items, item => item.jid, item => tpl_item(o, item))}
${ repeat(o.items, (item) => item.jid, (item) => tpl_item(o, item)) }
</ul>
`;
}

View File

@ -1,6 +1,7 @@
import { html } from "lit";
import { __ } from 'i18n';
import tpl_occupant from "./occupant.js";
import { __ } from 'i18n';
import { html } from "lit";
import { repeat } from 'lit/directives/repeat.js';
export default (o) => {
@ -15,6 +16,6 @@ export default (o) => {
</div>
</div>
<div class="dragresize dragresize-occupants-left"></div>
<ul class="occupant-list">${o.occupants.map(occ => tpl_occupant(occ, o))}</ul>
<ul class="occupant-list">${ repeat(o.occupants, (occ) => occ.get('jid'), (occ) => tpl_occupant(occ, o)) }</ul>
`;
}

View File

@ -3,6 +3,7 @@ import { __ } from 'i18n';
import { _converse, converse } from "@converse/headless/core";
import { html } from "lit";
import { isUniView } from '@converse/headless/utils/core.js';
import { repeat } from 'lit/directives/repeat.js';
import { toggleGroup } from '../utils.js';
const { u } = converse.env;
@ -56,7 +57,7 @@ export default (o) => {
<converse-icon color="var(--chat-head-color-dark)" size="1em" class="fa ${ (collapsed.includes(o.name)) ? 'fa-caret-right' : 'fa-caret-down' }"></converse-icon> ${o.name}
</a>
<ul class="items-list roster-group-contacts ${ (collapsed.includes(o.name)) ? 'collapsed' : '' }" data-group="${o.name}">
${ o.contacts.map(renderContact) }
${ repeat(o.contacts, (c) => c.get('jid'), renderContact) }
</ul>
</div>`;
}

View File

@ -44,19 +44,13 @@ export default (el) => {
<converse-icon class="fa fa-user-plus right" size="1.25em"></converse-icon>
</a>` : '' }
</div>
<div class="list-container roster-contacts ${ is_closed ? 'hidden' : '' }">
<converse-roster-filter @update=${() => el.requestUpdate()}></converse-roster-filter>
${ repeat(groupnames, n => n, name => {
${ repeat(groupnames, (n) => n, (name) => {
const contacts = contacts_map[name].filter(c => shouldShowContact(c, name));
contacts.sort(contactsComparator);
if (contacts.length) {
return tpl_group({
'contacts': contacts,
'name': name,
});
} else {
return '';
}
return contacts.length ? tpl_group({ contacts, name }) : '';
}) }
</div>
`;