* Take client creating functionality out of MatrixClientPeg Because all sorts of things import MatrixClientPeg and this means they pull in all manner of things related to creating a client, when all they need is to get the current one (and specifically it fixes the import cycle that means I can't add my test). * Remove fake indexeddb as it seems like the tests didn't actually see the fake indexeddb before (somehow) but now do, and are failing because they have that but no postmessage. It feels like the right solution is for these tests to not need indexeddb. * Don't mock a refresh token The tests don't mock out enough for the token refreshing setup to work, it wa somehow always ending up as null previously and now wasn't, at which point it broke, so just make it actually unset. * Move formatter instatiation to lazy rather than eagerly at parse time, as this apparently shifted one test to hit this import cycle instead. * Tests for room name generator
109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
|
Copyright 2016 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE files in the repository root for full details.
|
|
*/
|
|
|
|
import { type ReactElement, type ReactNode } from "react";
|
|
import { useIdColorHash } from "@vector-im/compound-web";
|
|
|
|
import { _t, getCurrentLanguage } from "../languageHandler";
|
|
import { getUserLanguage } from "../i18n/settings";
|
|
import { jsxJoin } from "./ReactUtils";
|
|
|
|
export { formatBytes } from "@element-hq/web-shared-components";
|
|
|
|
let cachedFormatter: Intl.NumberFormat | undefined;
|
|
let cachedCompactFormatter: Intl.NumberFormat | undefined;
|
|
|
|
// It's quite costly to instanciate `Intl.NumberFormat`, hence why we do not do
|
|
// it in every function call
|
|
const getCachedCompactFormatter = (): Intl.NumberFormat => {
|
|
if (!cachedCompactFormatter)
|
|
cachedCompactFormatter = new Intl.NumberFormat(getCurrentLanguage(), { notation: "compact" });
|
|
return cachedCompactFormatter;
|
|
};
|
|
|
|
/**
|
|
* formats and rounds numbers to fit into ~3 characters, suitable for badge counts
|
|
* e.g: 999, 10K, 99K, 1M, 10M, 99M, 1B, 10B, ...
|
|
*/
|
|
export function formatCount(count: number): string {
|
|
return getCachedCompactFormatter().format(count);
|
|
}
|
|
|
|
// It's quite costly to instanciate `Intl.NumberFormat`, hence why we do not do
|
|
// it in every function call
|
|
const getCachedFormatter = (): Intl.NumberFormat => {
|
|
if (!cachedFormatter) cachedFormatter = new Intl.NumberFormat(getCurrentLanguage());
|
|
return cachedFormatter;
|
|
};
|
|
|
|
/**
|
|
* Format a count showing the whole number but making it a bit more readable.
|
|
* e.g: 1000 => 1,000
|
|
*/
|
|
export function formatCountLong(count: number): string {
|
|
return getCachedFormatter().format(count);
|
|
}
|
|
|
|
export function getUserNameColorClass(userId: string): string {
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
const number = useIdColorHash(userId);
|
|
return `mx_Username_color${number}`;
|
|
}
|
|
|
|
/**
|
|
* Constructs a written English string representing `items`, with an optional
|
|
* limit on the number of items included in the result. If specified and if the
|
|
* length of `items` is greater than the limit, the string "and n others" will
|
|
* be appended onto the result. If `items` is empty, returns the empty string.
|
|
* If there is only one item, return it.
|
|
* @param {string[]} items the items to construct a string from.
|
|
* @param {number?} itemLimit the number by which to limit the list.
|
|
* @returns {string} a string constructed by joining `items` with a comma
|
|
* between each item, but with the last item appended as " and [lastItem]".
|
|
*/
|
|
export function formatList(items: string[], itemLimit?: number, includeCount?: boolean): string;
|
|
export function formatList(items: ReactElement[], itemLimit?: number, includeCount?: boolean): ReactElement;
|
|
export function formatList(items: ReactNode[], itemLimit?: number, includeCount?: boolean): ReactNode;
|
|
export function formatList(items: ReactNode[], itemLimit = items.length, includeCount = false): ReactNode {
|
|
let remaining = Math.max(items.length - itemLimit, 0);
|
|
if (items.length <= 1) {
|
|
return items[0] ?? "";
|
|
}
|
|
|
|
const formatter = new Intl.ListFormat(getUserLanguage(), { style: "long", type: "conjunction" });
|
|
if (remaining > 0) {
|
|
if (includeCount) {
|
|
itemLimit--;
|
|
remaining++;
|
|
}
|
|
|
|
items = items.slice(0, itemLimit);
|
|
let joinedItems: ReactNode;
|
|
if (items.every((e) => typeof e === "string")) {
|
|
joinedItems = items.join(", ");
|
|
} else {
|
|
joinedItems = jsxJoin(items, ", ");
|
|
}
|
|
|
|
return _t("items_and_n_others", { count: remaining }, { Items: () => joinedItems });
|
|
}
|
|
|
|
if (items.every((e) => typeof e === "string")) {
|
|
return formatter.format(items as string[]);
|
|
}
|
|
|
|
const parts = formatter.formatToParts(items.map((_, i) => `${i}`));
|
|
return jsxJoin(
|
|
parts.map((part) => {
|
|
if (part.type === "literal") return part.value;
|
|
return items[parseInt(part.value, 10)];
|
|
}),
|
|
);
|
|
}
|