2016-09-17 02:39:19 +01:00
|
|
|
/*
|
2024-09-09 14:57:16 +01:00
|
|
|
Copyright 2024 New Vector Ltd.
|
2020-06-08 12:14:10 -06:00
|
|
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
2024-09-09 14:57:16 +01:00
|
|
|
Copyright 2016 OpenMarket Ltd
|
2016-09-17 02:39:19 +01:00
|
|
|
|
2025-01-06 11:18:54 +00:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
2024-09-09 14:57:16 +01:00
|
|
|
Please see LICENSE files in the repository root for full details.
|
2016-09-17 02:39:19 +01:00
|
|
|
*/
|
|
|
|
|
|
2025-02-05 13:25:06 +00:00
|
|
|
import { type ReactElement, type ReactNode } from "react";
|
2023-08-24 18:12:28 +01:00
|
|
|
import { useIdColorHash } from "@vector-im/compound-web";
|
2023-02-13 11:39:16 +00:00
|
|
|
|
2023-09-25 12:18:15 +01:00
|
|
|
import { _t, getCurrentLanguage, getUserLanguage } from "../languageHandler";
|
2021-07-22 07:47:04 +02:00
|
|
|
import { jsxJoin } from "./ReactUtils";
|
2025-08-07 11:02:49 +02:00
|
|
|
|
2025-11-03 16:26:47 +00:00
|
|
|
export { formatBytes } from "@element-hq/web-shared-components";
|
2025-08-07 11:02:49 +02:00
|
|
|
|
2023-08-08 12:45:20 +01:00
|
|
|
const locale = getCurrentLanguage();
|
|
|
|
|
|
|
|
|
|
// It's quite costly to instanciate `Intl.NumberFormat`, hence why we do not do
|
|
|
|
|
// it in every function call
|
|
|
|
|
const compactFormatter = new Intl.NumberFormat(locale, {
|
|
|
|
|
notation: "compact",
|
|
|
|
|
});
|
2019-05-20 15:16:12 +01:00
|
|
|
|
2016-09-17 02:39:19 +01:00
|
|
|
/**
|
2023-08-08 12:45:20 +01:00
|
|
|
* formats and rounds numbers to fit into ~3 characters, suitable for badge counts
|
|
|
|
|
* e.g: 999, 10K, 99K, 1M, 10M, 99M, 1B, 10B, ...
|
2016-09-17 02:39:19 +01:00
|
|
|
*/
|
2020-06-08 12:14:10 -06:00
|
|
|
export function formatCount(count: number): string {
|
2023-08-08 12:45:20 +01:00
|
|
|
return compactFormatter.format(count);
|
2017-01-20 14:22:27 +00:00
|
|
|
}
|
2017-05-22 12:01:09 +01:00
|
|
|
|
2023-08-08 12:45:20 +01:00
|
|
|
// It's quite costly to instanciate `Intl.NumberFormat`, hence why we do not do
|
|
|
|
|
// it in every function call
|
|
|
|
|
const formatter = new Intl.NumberFormat(locale);
|
|
|
|
|
|
2020-01-24 16:13:55 +01:00
|
|
|
/**
|
|
|
|
|
* Format a count showing the whole number but making it a bit more readable.
|
|
|
|
|
* e.g: 1000 => 1,000
|
|
|
|
|
*/
|
2020-06-08 12:14:10 -06:00
|
|
|
export function formatCountLong(count: number): string {
|
2020-06-18 14:32:43 +01:00
|
|
|
return formatter.format(count);
|
2020-01-24 16:13:55 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-24 18:12:28 +01:00
|
|
|
export function getUserNameColorClass(userId: string): string {
|
|
|
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
|
|
|
const number = useIdColorHash(userId);
|
|
|
|
|
return `mx_Username_color${number}`;
|
2019-04-17 10:21:30 +02:00
|
|
|
}
|
2019-05-20 15:16:12 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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]".
|
|
|
|
|
*/
|
2023-09-25 12:18:15 +01:00
|
|
|
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++;
|
2021-09-17 11:36:22 +01:00
|
|
|
}
|
|
|
|
|
|
2023-09-25 12:18:15 +01:00
|
|
|
items = items.slice(0, itemLimit);
|
|
|
|
|
let joinedItems: ReactNode;
|
2021-09-17 11:36:22 +01:00
|
|
|
if (items.every((e) => typeof e === "string")) {
|
|
|
|
|
joinedItems = items.join(", ");
|
|
|
|
|
} else {
|
|
|
|
|
joinedItems = jsxJoin(items, ", ");
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-25 18:12:41 +01:00
|
|
|
return _t("items_and_n_others", { count: remaining }, { Items: () => joinedItems });
|
2019-05-20 15:16:12 +01:00
|
|
|
}
|
2023-09-25 12:18:15 +01:00
|
|
|
|
|
|
|
|
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)];
|
|
|
|
|
}),
|
|
|
|
|
);
|
2019-05-20 15:16:12 +01:00
|
|
|
}
|