Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #6130 from matrix-org/gsouquet/member-list-sort
Browse files Browse the repository at this point in the history
  • Loading branch information
germain-gg authored Jun 2, 2021
2 parents 2d50325 + f3431fe commit 96f5d3a
Show file tree
Hide file tree
Showing 11 changed files with 46 additions and 27 deletions.
3 changes: 2 additions & 1 deletion src/components/views/dialogs/InviteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {mediaFromMxc} from "../../../customisations/Media";
import {getAddressType} from "../../../UserAddress";
import BaseAvatar from '../avatars/BaseAvatar';
import AccessibleButton from '../elements/AccessibleButton';
import { compare } from '../../../utils/strings';

// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
Expand Down Expand Up @@ -578,7 +579,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
members.sort((a, b) => {
if (a.score === b.score) {
if (a.numRooms === b.numRooms) {
return a.member.userId.localeCompare(b.member.userId);
return compare(a.member.userId, b.member.userId);
}

return b.numRooms - a.numRooms;
Expand Down
3 changes: 2 additions & 1 deletion src/components/views/directory/NetworkDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { SettingLevel } from "../../../settings/SettingLevel";
import TextInputDialog from "../dialogs/TextInputDialog";
import QuestionDialog from "../dialogs/QuestionDialog";
import UIStore from "../../../stores/UIStore";
import { compare } from "../../../utils/strings";

export const ALL_ROOMS = Symbol("ALL_ROOMS");

Expand Down Expand Up @@ -187,7 +188,7 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s

protocolsList.forEach(({instances=[]}) => {
[...instances].sort((b, a) => {
return a.desc.localeCompare(b.desc);
return compare(a.desc, b.desc);
}).forEach(({desc, instance_id: instanceId}) => {
entries.push(
<MenuItemRadio
Expand Down
28 changes: 13 additions & 15 deletions src/components/views/rooms/MemberList.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ export default class MemberList extends React.Component {
member.user = cli.getUser(member.userId);
}

member.sortName = (member.name[0] === '@' ? member.name.substr(1) : member.name).replace(SORT_REGEX, "");

// XXX: this user may have no lastPresenceTs value!
// the right solution here is to fix the race rather than leave it as 0
});
Expand All @@ -252,6 +254,8 @@ export default class MemberList extends React.Component {
m.membership === 'join' || m.membership === 'invite'
);
});
const language = SettingsStore.getValue("language");
this.collator = new Intl.Collator(language, { sensitivity: 'base', usePunctuation: true });
filteredAndSortedMembers.sort(this.memberSort);
return filteredAndSortedMembers;
}
Expand Down Expand Up @@ -351,13 +355,7 @@ export default class MemberList extends React.Component {
}

// Fourth by name (alphabetical)
const nameA = (memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name).replace(SORT_REGEX, "");
const nameB = (memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name).replace(SORT_REGEX, "");
// console.log(`Comparing userA_name=${nameA} against userB_name=${nameB} - returning`);
return nameA.localeCompare(nameB, {
ignorePunctuation: true,
sensitivity: "base",
});
return this.collator.compare(memberA.sortName, memberB.sortName);
};

onSearchQueryChanged = searchQuery => {
Expand Down Expand Up @@ -422,7 +420,7 @@ export default class MemberList extends React.Component {
} else {
// Is a 3pid invite
return <EntityTile key={m.getStateKey()} name={m.getContent().display_name} suppressOnHover={true}
onClick={() => this._onPending3pidInviteClick(m)} />;
onClick={() => this._onPending3pidInviteClick(m)} />;
}
});
}
Expand Down Expand Up @@ -484,10 +482,10 @@ export default class MemberList extends React.Component {
if (this._getChildCountInvited() > 0) {
invitedHeader = <h2>{ _t("Invited") }</h2>;
invitedSection = <TruncatedList className="mx_MemberList_section mx_MemberList_invited" truncateAt={this.state.truncateAtInvited}
createOverflowElement={this._createOverflowTileInvited}
getChildren={this._getChildrenInvited}
getChildCount={this._getChildCountInvited}
/>;
createOverflowElement={this._createOverflowTileInvited}
getChildren={this._getChildrenInvited}
getChildCount={this._getChildCountInvited}
/>;
}

const footer = (
Expand Down Expand Up @@ -520,9 +518,9 @@ export default class MemberList extends React.Component {
>
<div className="mx_MemberList_wrapper">
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}
createOverflowElement={this._createOverflowTileJoined}
getChildren={this._getChildrenJoined}
getChildCount={this._getChildCountJoined} />
createOverflowElement={this._createOverflowTileJoined}
getChildren={this._getChildrenJoined}
getChildCount={this._getChildCountJoined} />
{ invitedHeader }
{ invitedSection }
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/views/rooms/WhoIsTypingTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Timer from '../../../utils/Timer';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import MemberAvatar from '../avatars/MemberAvatar';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { compare } from "../../../utils/strings";

interface IProps {
// the room this statusbar is representing.
Expand Down Expand Up @@ -207,7 +208,7 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
usersTyping = usersTyping.concat(stoppedUsersOnTimer);
// sort them so the typing members don't change order when
// moved to delayedStopTypingTimers
usersTyping.sort((a, b) => a.name.localeCompare(b.name));
usersTyping.sort((a, b) => compare(a.name, b.name));

const typingString = WhoIsTyping.whoIsTypingString(
usersTyping,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {EventType} from "matrix-js-sdk/src/@types/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { compare } from "../../../../../utils/strings";

const plEventsToLabels = {
// These will be translated for us later.
Expand Down Expand Up @@ -312,7 +313,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
// comparator for sorting PL users lexicographically on PL descending, MXID ascending. (case-insensitive)
const comparator = (a, b) => {
const plDiff = userLevels[b.key] - userLevels[a.key];
return plDiff !== 0 ? plDiff : a.key.toLocaleLowerCase().localeCompare(b.key.toLocaleLowerCase());
return plDiff !== 0 ? plDiff : compare(a.key.toLocaleLowerCase(), b.key.toLocaleLowerCase());
};

privilegedUsers.sort(comparator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ import Field from '../../../elements/Field';
import EventTilePreview from '../../../elements/EventTilePreview';
import StyledRadioGroup from "../../../elements/StyledRadioGroup";
import { SettingLevel } from "../../../../../settings/SettingLevel";
import {UIFeature} from "../../../../../settings/UIFeature";
import {Layout} from "../../../../../settings/Layout";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import { UIFeature } from "../../../../../settings/UIFeature";
import { Layout } from "../../../../../settings/Layout";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import { compare } from "../../../../../utils/strings";

interface IProps {
}
Expand Down Expand Up @@ -295,7 +296,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
.map(p => ({id: p[0], name: p[1]})); // convert pairs to objects for code readability
const builtInThemes = themes.filter(p => !p.id.startsWith("custom-"));
const customThemes = themes.filter(p => !builtInThemes.includes(p))
.sort((a, b) => a.name.localeCompare(b.name));
.sort((a, b) => compare(a.name, b.name));
const orderedThemes = [...builtInThemes, ...customThemes];
return (
<div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_themeSection">
Expand Down
3 changes: 2 additions & 1 deletion src/integrations/IntegrationManagers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import WidgetUtils from "../utils/WidgetUtils";
import {MatrixClientPeg} from "../MatrixClientPeg";
import SettingsStore from "../settings/SettingsStore";
import url from 'url';
import { compare } from "../utils/strings";

const KIND_PREFERENCE = [
// Ordered: first is most preferred, last is least preferred.
Expand Down Expand Up @@ -152,7 +153,7 @@ export class IntegrationManagers {

if (kind === Kind.Account) {
// Order by state_keys (IDs)
managers.sort((a, b) => a.id.localeCompare(b.id));
managers.sort((a, b) => compare(a.id, b.id));
}

ordered.push(...managers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { TagID } from "../../models";
import { IAlgorithm } from "./IAlgorithm";
import { compare } from "../../../../utils/strings";

/**
* Sorts rooms according to the browser's determination of alphabetic.
*/
export class AlphabeticAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
return rooms.sort((a, b) => {
return a.name.localeCompare(b.name);
return compare(a.name, b.name);
});
}
}
3 changes: 2 additions & 1 deletion src/stores/widgets/WidgetLayoutStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SettingLevel } from "../../settings/SettingLevel";
import { arrayFastClone } from "../../utils/arrays";
import { UPDATE_EVENT } from "../AsyncStore";
import { compare } from "../../utils/strings";

export const WIDGET_LAYOUT_EVENT_TYPE = "io.element.widgets.layout";

Expand Down Expand Up @@ -240,7 +241,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {

if (orderA === orderB) {
// We just need a tiebreak
return a.id.localeCompare(b.id);
return compare(a.id, b.id);
}

return orderA - orderB;
Expand Down
11 changes: 11 additions & 0 deletions src/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,14 @@ export function copyNode(ref: Element): boolean {
selectText(ref);
return document.execCommand('copy');
}


const collator = new Intl.Collator();
/**
* Performant language-sensitive string comparison
* @param a the first string to compare
* @param b the second string to compare
*/
export function compare(a: string, b: string): number {
return collator.compare(a, b);
}
4 changes: 3 additions & 1 deletion test/components/views/rooms/MemberList-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import sdk from '../../../skinned-sdk';

import {Room, RoomMember, User} from 'matrix-js-sdk';

import { compare } from "../../../../src/utils/strings";

function generateRoomId() {
return '!' + Math.random().toString().slice(2, 10) + ':domain';
}
Expand Down Expand Up @@ -173,7 +175,7 @@ describe('MemberList', () => {
if (!groupChange) {
const nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name;
const nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name;
const nameCompare = nameB.localeCompare(nameA);
const nameCompare = compare(nameB, nameA);
console.log("Comparing name");
expect(nameCompare).toBeGreaterThanOrEqual(0);
} else {
Expand Down

0 comments on commit 96f5d3a

Please sign in to comment.