Merge branch 'develop' into germain-gg/notifications-labs
This commit is contained in:
commit
9ceac6eae1
148 changed files with 14554 additions and 11595 deletions
|
@ -1240,10 +1240,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
<span>
|
||||
{isSpace
|
||||
? _t("Are you sure you want to leave the space '%(spaceName)s'?", {
|
||||
spaceName: roomToLeave?.name ?? _t("Unnamed Space"),
|
||||
spaceName: roomToLeave?.name ?? _t("common|unnamed_space"),
|
||||
})
|
||||
: _t("Are you sure you want to leave the room '%(roomName)s'?", {
|
||||
roomName: roomToLeave?.name ?? _t("Unnamed Room"),
|
||||
roomName: roomToLeave?.name ?? _t("common|unnamed_room"),
|
||||
})}
|
||||
{warnings}
|
||||
</span>
|
||||
|
|
|
@ -119,7 +119,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||
room.name ||
|
||||
room.canonical_alias ||
|
||||
room.aliases?.[0] ||
|
||||
(room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room"));
|
||||
(room.room_type === RoomType.Space ? _t("common|unnamed_space") : _t("common|unnamed_room"));
|
||||
|
||||
const [showChildren, toggleShowChildren] = useStateToggle(true);
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex();
|
||||
|
|
|
@ -63,13 +63,13 @@ function tooltipText(variant: Icon): string | undefined {
|
|||
case Icon.Globe:
|
||||
return _t("This room is public");
|
||||
case Icon.PresenceOnline:
|
||||
return _t("Online");
|
||||
return _t("presence|online");
|
||||
case Icon.PresenceAway:
|
||||
return _t("Away");
|
||||
return _t("presence|away");
|
||||
case Icon.PresenceOffline:
|
||||
return _t("common|offline");
|
||||
return _t("presence|offline");
|
||||
case Icon.PresenceBusy:
|
||||
return _t("Busy");
|
||||
return _t("presence|busy");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ const RoomCallBannerInner: React.FC<RoomCallBannerProps> = ({ roomId, call }) =>
|
|||
return (
|
||||
<div className="mx_RoomCallBanner" onClick={onClick}>
|
||||
<div className="mx_RoomCallBanner_text">
|
||||
<span className="mx_RoomCallBanner_label">{_t("Video call")}</span>
|
||||
<span className="mx_RoomCallBanner_label">{_t("voip|video_call")}</span>
|
||||
<GroupCallDuration groupCall={call.groupCall} />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
|||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import LabelledCheckbox from "../elements/LabelledCheckbox";
|
||||
|
||||
interface IProps {
|
||||
type?: RoomType;
|
||||
|
@ -45,15 +46,46 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
/**
|
||||
* The selected room join rule.
|
||||
*/
|
||||
joinRule: JoinRule;
|
||||
isPublic: boolean;
|
||||
/**
|
||||
* Indicates whether the created room should have public visibility (ie, it should be
|
||||
* shown in the public room list). Only applicable if `joinRule` == `JoinRule.Knock`.
|
||||
*/
|
||||
isPublicKnockRoom: boolean;
|
||||
/**
|
||||
* Indicates whether end-to-end encryption is enabled for the room.
|
||||
*/
|
||||
isEncrypted: boolean;
|
||||
/**
|
||||
* The room name.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The room topic.
|
||||
*/
|
||||
topic: string;
|
||||
/**
|
||||
* The room alias.
|
||||
*/
|
||||
alias: string;
|
||||
/**
|
||||
* Indicates whether the details section is open.
|
||||
*/
|
||||
detailsOpen: boolean;
|
||||
/**
|
||||
* Indicates whether federation is disabled for the room.
|
||||
*/
|
||||
noFederate: boolean;
|
||||
/**
|
||||
* Indicates whether the room name is valid.
|
||||
*/
|
||||
nameIsValid: boolean;
|
||||
/**
|
||||
* Indicates whether the user can change encryption settings for the room.
|
||||
*/
|
||||
canChangeEncryption: boolean;
|
||||
}
|
||||
|
||||
|
@ -78,7 +110,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
this.state = {
|
||||
isPublic: this.props.defaultPublic || false,
|
||||
isPublicKnockRoom: this.props.defaultPublic || false,
|
||||
isEncrypted: this.props.defaultEncrypted ?? privateShouldBeEncrypted(cli),
|
||||
joinRule,
|
||||
name: this.props.defaultName || "",
|
||||
|
@ -129,6 +161,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
|
||||
if (this.state.joinRule === JoinRule.Knock) {
|
||||
opts.joinRule = JoinRule.Knock;
|
||||
createOpts.visibility = this.state.isPublicKnockRoom ? Visibility.Public : Visibility.Private;
|
||||
}
|
||||
|
||||
return opts;
|
||||
|
@ -215,6 +248,10 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
return result;
|
||||
};
|
||||
|
||||
private onIsPublicKnockRoomChange = (isPublicKnockRoom: boolean): void => {
|
||||
this.setState({ isPublicKnockRoom });
|
||||
};
|
||||
|
||||
private static validateRoomName = withValidation({
|
||||
rules: [
|
||||
{
|
||||
|
@ -251,7 +288,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
"Everyone in <SpaceName/> will be able to find and join this room.",
|
||||
{},
|
||||
{
|
||||
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("Unnamed Space")}</b>,
|
||||
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("common|unnamed_space")}</b>,
|
||||
},
|
||||
)}
|
||||
|
||||
|
@ -265,7 +302,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
"Anyone will be able to find and join this room, not just members of <SpaceName/>.",
|
||||
{},
|
||||
{
|
||||
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("Unnamed Space")}</b>,
|
||||
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("common|unnamed_space")}</b>,
|
||||
},
|
||||
)}
|
||||
|
||||
|
@ -298,6 +335,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
let visibilitySection: JSX.Element | undefined;
|
||||
if (this.state.joinRule === JoinRule.Knock) {
|
||||
visibilitySection = (
|
||||
<LabelledCheckbox
|
||||
className="mx_CreateRoomDialog_labelledCheckbox"
|
||||
label={_t("Make this room visible in the public room directory.")}
|
||||
onChange={this.onIsPublicKnockRoomChange}
|
||||
value={this.state.isPublicKnockRoom}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let e2eeSection: JSX.Element | undefined;
|
||||
if (this.state.joinRule !== JoinRule.Public) {
|
||||
let microcopy: string;
|
||||
|
@ -341,11 +390,14 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
|
||||
let title: string;
|
||||
if (isVideoRoom) {
|
||||
title = _t("Create a video room");
|
||||
title = _t("create_room|title_video_room");
|
||||
} else if (this.props.parentSpace || this.state.joinRule === JoinRule.Knock) {
|
||||
title = _t("action|create_a_room");
|
||||
} else {
|
||||
title = this.state.joinRule === JoinRule.Public ? _t("Create a public room") : _t("Create a private room");
|
||||
title =
|
||||
this.state.joinRule === JoinRule.Public
|
||||
? _t("create_room|title_public_room")
|
||||
: _t("create_room|title_private_room");
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -383,6 +435,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
/>
|
||||
|
||||
{publicPrivateLabel}
|
||||
{visibilitySection}
|
||||
{e2eeSection}
|
||||
{aliasField}
|
||||
<details onToggle={this.onDetailsToggled} className="mx_CreateRoomDialog_details">
|
||||
|
@ -401,7 +454,9 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
</div>
|
||||
</form>
|
||||
<DialogButtons
|
||||
primaryButton={isVideoRoom ? _t("Create video room") : _t("Create room")}
|
||||
primaryButton={
|
||||
isVideoRoom ? _t("create_room|action_create_video_room") : _t("create_room|action_create_room")
|
||||
}
|
||||
onPrimaryButtonClick={this.onOk}
|
||||
onCancel={this.onCancel}
|
||||
/>
|
||||
|
|
|
@ -41,8 +41,8 @@ enum Category {
|
|||
}
|
||||
|
||||
const categoryLabels: Record<Category, TranslationKey> = {
|
||||
[Category.Room]: _td("common|room"),
|
||||
[Category.Other]: _td("Other"),
|
||||
[Category.Room]: _td("devtools|category_room"),
|
||||
[Category.Other]: _td("devtools|category_other"),
|
||||
};
|
||||
|
||||
export type Tool = React.FC<IDevtoolsProps> | ((props: IDevtoolsProps) => JSX.Element);
|
||||
|
|
|
@ -1338,10 +1338,10 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
|||
const isSpace = room?.isSpaceRoom();
|
||||
title = isSpace
|
||||
? _t("Invite to %(spaceName)s", {
|
||||
spaceName: room?.name || _t("Unnamed Space"),
|
||||
spaceName: room?.name || _t("common|unnamed_space"),
|
||||
})
|
||||
: _t("Invite to %(roomName)s", {
|
||||
roomName: room?.name || _t("Unnamed Room"),
|
||||
roomName: room?.name || _t("common|unnamed_room"),
|
||||
});
|
||||
|
||||
let helpTextUntranslated;
|
||||
|
|
|
@ -84,7 +84,7 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
|||
|
||||
return (
|
||||
<BaseDialog
|
||||
title={_t("Settings - %(spaceName)s", { spaceName: space.name || _t("Unnamed Space") })}
|
||||
title={_t("Settings - %(spaceName)s", { spaceName: space.name || _t("common|unnamed_space") })}
|
||||
className="mx_SpaceSettingsDialog"
|
||||
contentId="mx_SpaceSettingsDialog"
|
||||
onFinished={onFinished}
|
||||
|
|
|
@ -31,7 +31,7 @@ export default class DialPadBackspaceButton extends React.PureComponent<IProps>
|
|||
<AccessibleButton
|
||||
className="mx_DialPadBackspaceButton"
|
||||
onClick={this.props.onBackspacePress}
|
||||
aria-label={_t("Backspace")}
|
||||
aria-label={_t("keyboard|backspace")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -27,22 +27,38 @@ interface IProps extends HTMLAttributes<HTMLSpanElement> {
|
|||
tooltipLabel?: string;
|
||||
tooltipShortcut?: string;
|
||||
children?: ReactNode;
|
||||
viewUserOnClick?: boolean;
|
||||
}
|
||||
|
||||
const FacePile: FC<IProps> = ({ members, size, overflow, tooltipLabel, tooltipShortcut, children, ...props }) => {
|
||||
const FacePile: FC<IProps> = ({
|
||||
members,
|
||||
size,
|
||||
overflow,
|
||||
tooltipLabel,
|
||||
tooltipShortcut,
|
||||
children,
|
||||
viewUserOnClick = true,
|
||||
...props
|
||||
}) => {
|
||||
const faces = members.map(
|
||||
tooltipLabel
|
||||
? (m) => <MemberAvatar key={m.userId} member={m} size={size} hideTitle />
|
||||
: (m) => (
|
||||
<Tooltip key={m.userId} label={m.name} shortcut={tooltipShortcut}>
|
||||
<MemberAvatar member={m} size={size} viewUserOnClick={!props.onClick} hideTitle />
|
||||
<MemberAvatar
|
||||
member={m}
|
||||
size={size}
|
||||
viewUserOnClick={!props.onClick && viewUserOnClick}
|
||||
hideTitle
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
);
|
||||
|
||||
const pileContents = (
|
||||
<>
|
||||
{overflow ? <span className="mx_FacePile_more" /> : null}
|
||||
{/* XXX: The margin-left is a workaround for Compound's styling excluding this element and being overly specific */}
|
||||
{overflow ? <span className="mx_FacePile_more" style={{ marginLeft: `calc(${size} * -0.2)` }} /> : null}
|
||||
{faces}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import StyledCheckbox from "./StyledCheckbox";
|
||||
|
||||
|
@ -29,11 +30,13 @@ interface IProps {
|
|||
disabled?: boolean;
|
||||
// The function to call when the value changes
|
||||
onChange(checked: boolean): void;
|
||||
// Optional additional CSS class to apply to the label
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange }) => {
|
||||
const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange, className }) => {
|
||||
return (
|
||||
<label className="mx_LabelledCheckbox">
|
||||
<label className={classnames("mx_LabelledCheckbox", className)}>
|
||||
<StyledCheckbox disabled={disabled} checked={value} onChange={(e) => onChange(e.target.checked)} />
|
||||
<div className="mx_LabelledCheckbox_labels">
|
||||
<span className="mx_LabelledCheckbox_label">{label}</span>
|
||||
|
|
|
@ -71,7 +71,7 @@ const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(
|
|||
</span>
|
||||
<LiveContentSummary
|
||||
type={LiveContentType.Video}
|
||||
text={_t("Video call")}
|
||||
text={_t("voip|video_call")}
|
||||
active={false}
|
||||
participantCount={participatingMembers.length}
|
||||
/>
|
||||
|
|
|
@ -117,7 +117,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
|
|||
<AccessibleTooltipButton
|
||||
className={silenceClass}
|
||||
onClick={this.props.callEventGrouper.toggleSilenced}
|
||||
title={this.state.silenced ? _t("Sound on") : _t("Silence call")}
|
||||
title={this.state.silenced ? _t("voip|unsilence") : _t("voip|silence")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
|
|||
// Also the correct hangup code as of VoIP v1 (with underscore)
|
||||
// Also, if we don't have a reason
|
||||
const duration = this.props.callEventGrouper.duration!;
|
||||
let text = _t("Call ended");
|
||||
let text = _t("timeline|m.call.hangup|dm");
|
||||
if (duration) {
|
||||
text += " • " + formatPreciseDuration(duration);
|
||||
}
|
||||
|
@ -268,7 +268,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
|
|||
const event = this.props.mxEvent;
|
||||
const sender = event.sender ? event.sender.name : event.getSender();
|
||||
const isVoice = this.props.callEventGrouper.isVoice;
|
||||
const callType = isVoice ? _t("Voice call") : _t("Video call");
|
||||
const callType = isVoice ? _t("voip|voice_call") : _t("voip|video_call");
|
||||
const callState = this.state.callState;
|
||||
const hangupReason = this.props.callEventGrouper.hangupReason;
|
||||
const content = this.renderContent();
|
||||
|
|
|
@ -25,13 +25,13 @@ import { IBodyProps } from "./IBodyProps";
|
|||
|
||||
const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent }, ref) => {
|
||||
const cli: MatrixClient = useContext(MatrixClientContext);
|
||||
let text = _t("Message deleted");
|
||||
let text = _t("timeline|self_redaction");
|
||||
const unsigned = mxEvent.getUnsigned();
|
||||
const redactedBecauseUserId = unsigned && unsigned.redacted_because && unsigned.redacted_because.sender;
|
||||
if (redactedBecauseUserId && redactedBecauseUserId !== mxEvent.getSender()) {
|
||||
const room = cli.getRoom(mxEvent.getRoomId());
|
||||
const sender = room && room.getMember(redactedBecauseUserId);
|
||||
text = _t("Message deleted by %(name)s", { name: sender ? sender.name : redactedBecauseUserId });
|
||||
text = _t("timeline|redaction", { name: sender ? sender.name : redactedBecauseUserId });
|
||||
}
|
||||
|
||||
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
|
||||
|
|
|
@ -343,14 +343,16 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
|
|||
{_t("common|people")}
|
||||
<span className="mx_BaseCard_Button_sublabel">{memberCount}</span>
|
||||
</Button>
|
||||
<Button
|
||||
className="mx_RoomSummaryCard_icon_search"
|
||||
onClick={() => {
|
||||
onSearchClick?.();
|
||||
}}
|
||||
>
|
||||
{_t("Search")}
|
||||
</Button>
|
||||
{SettingsStore.getValue("feature_new_room_decoration_ui") && (
|
||||
<Button
|
||||
className="mx_RoomSummaryCard_icon_search"
|
||||
onClick={() => {
|
||||
onSearchClick?.();
|
||||
}}
|
||||
>
|
||||
{_t("Search")}
|
||||
</Button>
|
||||
)}
|
||||
{!isVideoRoom && (
|
||||
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
|
||||
{_t("Files")}
|
||||
|
|
|
@ -112,8 +112,8 @@ const VoiceCallButton: FC<VoiceCallButtonProps> = ({ room, busy, setBusy, behavi
|
|||
<AccessibleTooltipButton
|
||||
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_voiceCallButton"
|
||||
onClick={onClick}
|
||||
title={_t("Voice call")}
|
||||
tooltip={tooltip ?? _t("Voice call")}
|
||||
title={_t("voip|voice_call")}
|
||||
tooltip={tooltip ?? _t("voip|voice_call")}
|
||||
alignment={Alignment.Bottom}
|
||||
disabled={disabled || busy}
|
||||
/>
|
||||
|
@ -228,8 +228,8 @@ const VideoCallButton: FC<VideoCallButtonProps> = ({ room, busy, setBusy, behavi
|
|||
inputRef={buttonRef}
|
||||
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_videoCallButton"
|
||||
onClick={onClick}
|
||||
title={_t("Video call")}
|
||||
tooltip={tooltip ?? _t("Video call")}
|
||||
title={_t("voip|video_call")}
|
||||
tooltip={tooltip ?? _t("voip|video_call")}
|
||||
alignment={Alignment.Bottom}
|
||||
disabled={disabled || busy}
|
||||
/>
|
||||
|
|
|
@ -78,7 +78,7 @@ function SendButton(props: ISendButtonProps): JSX.Element {
|
|||
<AccessibleTooltipButton
|
||||
className="mx_MessageComposer_sendMessage"
|
||||
onClick={props.onClick}
|
||||
title={props.title ?? _t("Send message")}
|
||||
title={props.title ?? _t("composer|send_button_title")}
|
||||
data-testid="sendmessagebtn"
|
||||
/>
|
||||
);
|
||||
|
@ -303,19 +303,19 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
|||
if (this.props.replyToEvent) {
|
||||
const replyingToThread = this.props.relation?.rel_type === THREAD_RELATION_TYPE.name;
|
||||
if (replyingToThread && this.props.e2eStatus) {
|
||||
return _t("Reply to encrypted thread…");
|
||||
return _t("composer|placeholder_thread_encrypted");
|
||||
} else if (replyingToThread) {
|
||||
return _t("Reply to thread…");
|
||||
return _t("composer|placeholder_thread");
|
||||
} else if (this.props.e2eStatus) {
|
||||
return _t("Send an encrypted reply…");
|
||||
return _t("composer|placeholder_reply_encrypted");
|
||||
} else {
|
||||
return _t("Send a reply…");
|
||||
return _t("composer|placeholder_reply");
|
||||
}
|
||||
} else {
|
||||
if (this.props.e2eStatus) {
|
||||
return _t("Send an encrypted message…");
|
||||
return _t("composer|placeholder_encrypted");
|
||||
} else {
|
||||
return _t("Send a message…");
|
||||
return _t("composer|placeholder");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ import React from "react";
|
|||
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { formatDuration } from "../../../DateUtils";
|
||||
|
||||
const BUSY_PRESENCE_NAME = new UnstableValue("busy", "org.matrix.msc3026.busy");
|
||||
|
||||
|
@ -37,47 +38,23 @@ export default class PresenceLabel extends React.Component<IProps> {
|
|||
activeAgo: -1,
|
||||
};
|
||||
|
||||
// Return duration as a string using appropriate time units
|
||||
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
|
||||
private getDuration(time: number): string | undefined {
|
||||
if (!time) return;
|
||||
const t = Math.round(time / 1000);
|
||||
const s = t % 60;
|
||||
const m = Math.round(t / 60) % 60;
|
||||
const h = Math.round(t / (60 * 60)) % 24;
|
||||
const d = Math.round(t / (60 * 60 * 24));
|
||||
if (t < 60) {
|
||||
if (t < 0) {
|
||||
return _t("%(duration)ss", { duration: 0 });
|
||||
}
|
||||
return _t("%(duration)ss", { duration: s });
|
||||
}
|
||||
if (t < 60 * 60) {
|
||||
return _t("%(duration)sm", { duration: m });
|
||||
}
|
||||
if (t < 24 * 60 * 60) {
|
||||
return _t("%(duration)sh", { duration: h });
|
||||
}
|
||||
return _t("%(duration)sd", { duration: d });
|
||||
}
|
||||
|
||||
private getPrettyPresence(presence?: string, activeAgo?: number, currentlyActive?: boolean): string {
|
||||
// for busy presence, we ignore the 'currentlyActive' flag: they're busy whether
|
||||
// they're active or not. It can be set while the user is active in which case
|
||||
// the 'active ago' ends up being 0.
|
||||
if (presence && BUSY_PRESENCE_NAME.matches(presence)) return _t("Busy");
|
||||
if (presence && BUSY_PRESENCE_NAME.matches(presence)) return _t("presence|busy");
|
||||
|
||||
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
|
||||
const duration = this.getDuration(activeAgo);
|
||||
if (presence === "online") return _t("Online for %(duration)s", { duration: duration });
|
||||
if (presence === "unavailable") return _t("Idle for %(duration)s", { duration: duration }); // XXX: is this actually right?
|
||||
if (presence === "offline") return _t("Offline for %(duration)s", { duration: duration });
|
||||
return _t("Unknown for %(duration)s", { duration: duration });
|
||||
const duration = formatDuration(activeAgo);
|
||||
if (presence === "online") return _t("presence|online_for", { duration: duration });
|
||||
if (presence === "unavailable") return _t("presence|idle_for", { duration: duration }); // XXX: is this actually right?
|
||||
if (presence === "offline") return _t("presence|offline_for", { duration: duration });
|
||||
return _t("presence|unknown_for", { duration: duration });
|
||||
} else {
|
||||
if (presence === "online") return _t("Online");
|
||||
if (presence === "unavailable") return _t("Idle"); // XXX: is this actually right?
|
||||
if (presence === "offline") return _t("common|offline");
|
||||
return _t("Unknown");
|
||||
if (presence === "online") return _t("presence|online");
|
||||
if (presence === "unavailable") return _t("presence|idle"); // XXX: is this actually right?
|
||||
if (presence === "offline") return _t("presence|offline");
|
||||
return _t("presence|unknown");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -177,44 +177,56 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
</Box>
|
||||
<Flex as="nav" align="center" gap="var(--cpd-space-2x)">
|
||||
{!useElementCallExclusively && (
|
||||
<IconButton
|
||||
disabled={!!voiceCallDisabledReason}
|
||||
title={!voiceCallDisabledReason ? _t("Voice call") : voiceCallDisabledReason!}
|
||||
onClick={() => {
|
||||
placeCall(room, CallType.Voice, voiceCallType);
|
||||
}}
|
||||
>
|
||||
<VoiceCallIcon />
|
||||
</IconButton>
|
||||
<Tooltip label={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}>
|
||||
<IconButton
|
||||
disabled={!!voiceCallDisabledReason}
|
||||
title={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
placeCall(room, CallType.Voice, voiceCallType);
|
||||
}}
|
||||
>
|
||||
<VoiceCallIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<IconButton
|
||||
disabled={!!videoCallDisabledReason}
|
||||
title={!videoCallDisabledReason ? _t("Video call") : videoCallDisabledReason!}
|
||||
onClick={() => {
|
||||
placeCall(room, CallType.Video, videoCallType);
|
||||
}}
|
||||
>
|
||||
<VideoCallIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
indicator={notificationColorToIndicator(threadNotifications)}
|
||||
onClick={() => {
|
||||
showOrHidePanel(RightPanelPhases.ThreadPanel);
|
||||
}}
|
||||
title={_t("common|threads")}
|
||||
>
|
||||
<ThreadsIcon />
|
||||
</IconButton>
|
||||
{notificationsEnabled && (
|
||||
<Tooltip label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}>
|
||||
<IconButton
|
||||
indicator={notificationColorToIndicator(globalNotificationState.color)}
|
||||
onClick={() => {
|
||||
showOrHidePanel(RightPanelPhases.NotificationPanel);
|
||||
disabled={!!videoCallDisabledReason}
|
||||
title={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
placeCall(room, CallType.Video, videoCallType);
|
||||
}}
|
||||
title={_t("Notifications")}
|
||||
>
|
||||
<NotificationsIcon />
|
||||
<VideoCallIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip label={_t("common|threads")}>
|
||||
<IconButton
|
||||
indicator={notificationColorToIndicator(threadNotifications)}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
showOrHidePanel(RightPanelPhases.ThreadPanel);
|
||||
}}
|
||||
title={_t("common|threads")}
|
||||
>
|
||||
<ThreadsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{notificationsEnabled && (
|
||||
<Tooltip label={_t("Notifications")}>
|
||||
<IconButton
|
||||
indicator={notificationColorToIndicator(globalNotificationState.color)}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
showOrHidePanel(RightPanelPhases.NotificationPanel);
|
||||
}}
|
||||
title={_t("Notifications")}
|
||||
>
|
||||
<NotificationsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Flex>
|
||||
{!isDirectMessage && (
|
||||
|
@ -233,6 +245,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
members={members.slice(0, 3)}
|
||||
size="20px"
|
||||
overflow={false}
|
||||
viewUserOnClick={false}
|
||||
>
|
||||
{formatCount(memberCount)}
|
||||
</FacePile>
|
||||
|
|
|
@ -33,7 +33,7 @@ export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
|
|||
const [disabled, setDisabled] = useState(false);
|
||||
const knockMembers = useTypedEventEmitterState(
|
||||
room,
|
||||
RoomStateEvent.Members,
|
||||
RoomStateEvent.Update,
|
||||
useCallback(() => room.getMembersWithMembership("knock"), [room]),
|
||||
);
|
||||
const knockMembersCount = knockMembers.length;
|
||||
|
|
|
@ -419,7 +419,7 @@ const TAG_AESTHETICS: TagAestheticsMap = {
|
|||
defaultHidden: false,
|
||||
},
|
||||
[DefaultTagID.ServerNotice]: {
|
||||
sectionLabel: _td("System Alerts"),
|
||||
sectionLabel: _td("common|system_alerts"),
|
||||
isInvite: false,
|
||||
defaultHidden: false,
|
||||
},
|
||||
|
|
|
@ -261,7 +261,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
|
|||
// Load stickerpack content
|
||||
if (!!stickerpickerWidget?.content?.url) {
|
||||
// Set default name
|
||||
stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack");
|
||||
stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("common|stickerpack");
|
||||
|
||||
// FIXME: could this use the same code as other apps?
|
||||
const stickerApp: IWidget = {
|
||||
|
|
|
@ -144,7 +144,7 @@ export const PeopleRoomSettingsTab: VFC<{ room: Room }> = ({ room }) => {
|
|||
|
||||
const knockMembers = useTypedEventEmitterState(
|
||||
room,
|
||||
RoomStateEvent.Members,
|
||||
RoomStateEvent.Update,
|
||||
useCallback(() => room.getMembersWithMembership("knock"), [room]),
|
||||
);
|
||||
|
||||
|
|
|
@ -249,68 +249,74 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
|
||||
const plEventsToLabels: Record<EventType | string, TranslationKey | null> = {
|
||||
// These will be translated for us later.
|
||||
[EventType.RoomAvatar]: isSpaceRoom ? _td("Change space avatar") : _td("Change room avatar"),
|
||||
[EventType.RoomName]: isSpaceRoom ? _td("Change space name") : _td("Change room name"),
|
||||
[EventType.RoomAvatar]: isSpaceRoom
|
||||
? _td("room_settings|permissions|m.room.avatar_space")
|
||||
: _td("room_settings|permissions|m.room.avatar"),
|
||||
[EventType.RoomName]: isSpaceRoom
|
||||
? _td("room_settings|permissions|m.room.name_space")
|
||||
: _td("room_settings|permissions|m.room.name"),
|
||||
[EventType.RoomCanonicalAlias]: isSpaceRoom
|
||||
? _td("Change main address for the space")
|
||||
: _td("Change main address for the room"),
|
||||
[EventType.SpaceChild]: _td("Manage rooms in this space"),
|
||||
[EventType.RoomHistoryVisibility]: _td("Change history visibility"),
|
||||
[EventType.RoomPowerLevels]: _td("Change permissions"),
|
||||
[EventType.RoomTopic]: isSpaceRoom ? _td("Change description") : _td("Change topic"),
|
||||
[EventType.RoomTombstone]: _td("Upgrade the room"),
|
||||
[EventType.RoomEncryption]: _td("Enable room encryption"),
|
||||
[EventType.RoomServerAcl]: _td("Change server ACLs"),
|
||||
[EventType.Reaction]: _td("Send reactions"),
|
||||
[EventType.RoomRedaction]: _td("Remove messages sent by me"),
|
||||
? _td("room_settings|permissions|m.room.canonical_alias_space")
|
||||
: _td("room_settings|permissions|m.room.canonical_alias"),
|
||||
[EventType.SpaceChild]: _td("room_settings|permissions|m.space.child"),
|
||||
[EventType.RoomHistoryVisibility]: _td("room_settings|permissions|m.room.history_visibility"),
|
||||
[EventType.RoomPowerLevels]: _td("room_settings|permissions|m.room.power_levels"),
|
||||
[EventType.RoomTopic]: isSpaceRoom
|
||||
? _td("room_settings|permissions|m.room.topic_space")
|
||||
: _td("room_settings|permissions|m.room.topic"),
|
||||
[EventType.RoomTombstone]: _td("room_settings|permissions|m.room.tombstone"),
|
||||
[EventType.RoomEncryption]: _td("room_settings|permissions|m.room.encryption"),
|
||||
[EventType.RoomServerAcl]: _td("room_settings|permissions|m.room.server_acl"),
|
||||
[EventType.Reaction]: _td("room_settings|permissions|m.reaction"),
|
||||
[EventType.RoomRedaction]: _td("room_settings|permissions|m.room.redaction"),
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
"im.vector.modular.widgets": isSpaceRoom ? null : _td("Modify widgets"),
|
||||
[VoiceBroadcastInfoEventType]: _td("Voice broadcasts"),
|
||||
"im.vector.modular.widgets": isSpaceRoom ? null : _td("room_settings|permissions|m.widget"),
|
||||
[VoiceBroadcastInfoEventType]: _td("room_settings|permissions|io.element.voice_broadcast_info"),
|
||||
};
|
||||
|
||||
if (SettingsStore.getValue("feature_pinning")) {
|
||||
plEventsToLabels[EventType.RoomPinnedEvents] = _td("Manage pinned events");
|
||||
plEventsToLabels[EventType.RoomPinnedEvents] = _td("room_settings|permissions|m.room.pinned_events");
|
||||
}
|
||||
// MSC3401: Native Group VoIP signaling
|
||||
if (SettingsStore.getValue("feature_group_calls")) {
|
||||
plEventsToLabels[ElementCall.CALL_EVENT_TYPE.name] = _td("Start %(brand)s calls");
|
||||
plEventsToLabels[ElementCall.MEMBER_EVENT_TYPE.name] = _td("Join %(brand)s calls");
|
||||
plEventsToLabels[ElementCall.CALL_EVENT_TYPE.name] = _td("room_settings|permissions|m.call");
|
||||
plEventsToLabels[ElementCall.MEMBER_EVENT_TYPE.name] = _td("room_settings|permissions|m.call.member");
|
||||
}
|
||||
|
||||
const powerLevelDescriptors: Record<string, IPowerLevelDescriptor> = {
|
||||
"users_default": {
|
||||
desc: _t("Default role"),
|
||||
desc: _t("room_settings|permissions|users_default"),
|
||||
defaultValue: 0,
|
||||
},
|
||||
"events_default": {
|
||||
desc: _t("Send messages"),
|
||||
desc: _t("room_settings|permissions|events_default"),
|
||||
defaultValue: 0,
|
||||
hideForSpace: true,
|
||||
},
|
||||
"invite": {
|
||||
desc: _t("Invite users"),
|
||||
desc: _t("room_settings|permissions|invite"),
|
||||
defaultValue: 0,
|
||||
},
|
||||
"state_default": {
|
||||
desc: _t("Change settings"),
|
||||
desc: _t("room_settings|permissions|state_default"),
|
||||
defaultValue: 50,
|
||||
},
|
||||
"kick": {
|
||||
desc: _t("Remove users"),
|
||||
desc: _t("room_settings|permissions|kick"),
|
||||
defaultValue: 50,
|
||||
},
|
||||
"ban": {
|
||||
desc: _t("Ban users"),
|
||||
desc: _t("room_settings|permissions|ban"),
|
||||
defaultValue: 50,
|
||||
},
|
||||
"redact": {
|
||||
desc: _t("Remove messages sent by others"),
|
||||
desc: _t("room_settings|permissions|redact"),
|
||||
defaultValue: 50,
|
||||
hideForSpace: true,
|
||||
},
|
||||
"notifications.room": {
|
||||
desc: _t("Notify everyone"),
|
||||
desc: _t("room_settings|permissions|notifications.room"),
|
||||
defaultValue: 50,
|
||||
hideForSpace: true,
|
||||
},
|
||||
|
|
|
@ -276,7 +276,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
|
||||
public render(): React.ReactNode {
|
||||
const secureBackup = (
|
||||
<SettingsSubsection heading={_t("Secure Backup")}>
|
||||
<SettingsSubsection heading={_t("common|secure_backup")}>
|
||||
<SecureBackupPanel />
|
||||
</SettingsSubsection>
|
||||
);
|
||||
|
@ -292,7 +292,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
// in having advanced details here once all flows are implemented, we
|
||||
// can remove this.
|
||||
const crossSigning = (
|
||||
<SettingsSubsection heading={_t("Cross-signing")}>
|
||||
<SettingsSubsection heading={_t("common|cross_signing")}>
|
||||
<CrossSigningPanel />
|
||||
</SettingsSubsection>
|
||||
);
|
||||
|
|
|
@ -259,23 +259,23 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
|
|||
kind="audio"
|
||||
devices={audioInputs}
|
||||
setDevice={setAudioInput}
|
||||
deviceListLabel={_t("Audio devices")}
|
||||
deviceListLabel={_t("voip|audio_devices")}
|
||||
muted={audioMuted}
|
||||
disabled={connecting}
|
||||
toggle={toggleAudio}
|
||||
unmutedTitle={_t("Mute microphone")}
|
||||
mutedTitle={_t("Unmute microphone")}
|
||||
unmutedTitle={_t("voip|disable_microphone")}
|
||||
mutedTitle={_t("voip|enable_microphone")}
|
||||
/>
|
||||
<DeviceButton
|
||||
kind="video"
|
||||
devices={videoInputs}
|
||||
setDevice={setVideoInput}
|
||||
deviceListLabel={_t("Video devices")}
|
||||
deviceListLabel={_t("voip|video_devices")}
|
||||
muted={videoMuted}
|
||||
disabled={connecting}
|
||||
toggle={toggleVideo}
|
||||
unmutedTitle={_t("Turn off camera")}
|
||||
mutedTitle={_t("Turn on camera")}
|
||||
unmutedTitle={_t("voip|disable_camera")}
|
||||
mutedTitle={_t("voip|enable_camera")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -66,7 +66,7 @@ class DialPadButton extends React.PureComponent<DigitButtonProps | DialButtonPro
|
|||
<AccessibleButton
|
||||
className="mx_DialPad_button mx_DialPad_dialButton"
|
||||
onClick={this.onClick}
|
||||
aria-label={_t("Dial")}
|
||||
aria-label={_t("voip|dial")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -404,11 +404,9 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
|||
const sharerName = primaryFeed?.getMember()?.name;
|
||||
if (!sharerName) return null;
|
||||
|
||||
let text = isScreensharing ? _t("You are presenting") : _t("%(sharerName)s is presenting", { sharerName });
|
||||
let text = isScreensharing ? _t("voip|you_are_presenting") : _t("voip|user_is_presenting", { sharerName });
|
||||
if (!sidebarShown) {
|
||||
text +=
|
||||
" • " +
|
||||
(call.isLocalVideoMuted() ? _t("Your camera is turned off") : _t("Your camera is still enabled"));
|
||||
text += " • " + (call.isLocalVideoMuted() ? _t("voip|camera_disabled") : _t("voip|camera_enabled"));
|
||||
}
|
||||
|
||||
return <div className="mx_LegacyCallView_toast">{text}</div>;
|
||||
|
@ -450,7 +448,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
|||
holdTransferContent = (
|
||||
<div className="mx_LegacyCallView_status">
|
||||
{_t(
|
||||
"Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>",
|
||||
"voip|consulting",
|
||||
{
|
||||
transferTarget: transferTargetName,
|
||||
transferee: transfereeName,
|
||||
|
@ -470,8 +468,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
|||
if (isRemoteOnHold) {
|
||||
onHoldText = _t(
|
||||
LegacyCallHandler.instance.hasAnyUnheldCall()
|
||||
? _td("You held the call <a>Switch</a>")
|
||||
: _td("You held the call <a>Resume</a>"),
|
||||
? _td("voip|call_held_switch")
|
||||
: _td("voip|call_held_resume"),
|
||||
{},
|
||||
{
|
||||
a: (sub) => (
|
||||
|
@ -482,7 +480,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
|||
},
|
||||
);
|
||||
} else if (isLocalOnHold) {
|
||||
onHoldText = _t("%(peerName)s held the call", {
|
||||
onHoldText = _t("voip|call_held", {
|
||||
peerName: call.getOpponentMember()?.name,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -269,15 +269,15 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
|
|||
inputRef={this.dialpadButton}
|
||||
onClick={this.onDialpadClick}
|
||||
isExpanded={this.state.showDialpad}
|
||||
title={_t("Dialpad")}
|
||||
title={_t("voip|dialpad")}
|
||||
alignment={Alignment.Top}
|
||||
/>
|
||||
)}
|
||||
<LegacyCallViewDropdownButton
|
||||
state={!this.props.buttonsState.micMuted}
|
||||
className="mx_LegacyCallViewButtons_button_mic"
|
||||
onLabel={_t("Mute the microphone")}
|
||||
offLabel={_t("Unmute the microphone")}
|
||||
onLabel={_t("voip|disable_microphone")}
|
||||
offLabel={_t("voip|enable_microphone")}
|
||||
onClick={this.props.handlers.onMicMuteClick}
|
||||
deviceKinds={[MediaDeviceKindEnum.AudioInput, MediaDeviceKindEnum.AudioOutput]}
|
||||
/>
|
||||
|
@ -285,8 +285,8 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
|
|||
<LegacyCallViewDropdownButton
|
||||
state={!this.props.buttonsState.vidMuted}
|
||||
className="mx_LegacyCallViewButtons_button_vid"
|
||||
onLabel={_t("Stop the camera")}
|
||||
offLabel={_t("Start the camera")}
|
||||
onLabel={_t("voip|disable_camera")}
|
||||
offLabel={_t("voip|enable_camera")}
|
||||
onClick={this.props.handlers.onVidMuteClick}
|
||||
deviceKinds={[MediaDeviceKindEnum.VideoInput]}
|
||||
/>
|
||||
|
@ -295,8 +295,8 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
|
|||
<LegacyCallViewToggleButton
|
||||
state={this.props.buttonsState.screensharing}
|
||||
className="mx_LegacyCallViewButtons_button_screensharing"
|
||||
onLabel={_t("Stop sharing your screen")}
|
||||
offLabel={_t("Start sharing your screen")}
|
||||
onLabel={_t("voip|stop_screenshare")}
|
||||
offLabel={_t("voip|start_screenshare")}
|
||||
onClick={this.props.handlers.onScreenshareClick}
|
||||
/>
|
||||
)}
|
||||
|
@ -322,7 +322,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
|
|||
<AccessibleTooltipButton
|
||||
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_button_hangup"
|
||||
onClick={this.props.handlers.onHangupClick}
|
||||
title={_t("Hangup")}
|
||||
title={_t("voip|hangup")}
|
||||
alignment={Alignment.Top}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@ const LegacyCallViewHeaderControls: React.FC<LegacyCallControlsProps> = ({ onExp
|
|||
<AccessibleTooltipButton
|
||||
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_fullscreen"
|
||||
onClick={onMaximize}
|
||||
title={_t("Fill screen")}
|
||||
title={_t("voip|maximise")}
|
||||
/>
|
||||
)}
|
||||
{onPin && (
|
||||
|
@ -48,7 +48,7 @@ const LegacyCallViewHeaderControls: React.FC<LegacyCallControlsProps> = ({ onExp
|
|||
<AccessibleTooltipButton
|
||||
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_expand"
|
||||
onClick={onExpand}
|
||||
title={_t("Return to call")}
|
||||
title={_t("voip|expand")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -64,7 +64,7 @@ const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
|
|||
<span className="mx_LegacyCallViewHeader_secondaryCallInfo">
|
||||
<RoomAvatar room={callRoom} size="16px" />
|
||||
<span className="mx_LegacyCallView_secondaryCall_roomName">
|
||||
{_t("%(name)s on hold", { name: callRoom.name })}
|
||||
{_t("voip|on_hold", { name: callRoom.name })}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue