Merge branch 'develop' into germain-gg/fix-right-panel-member
This commit is contained in:
commit
1b8d00d1d4
141 changed files with 6493 additions and 5556 deletions
|
@ -57,6 +57,7 @@ interface RoomlessProps extends BaseProps {
|
|||
interface RoomProps extends BaseProps {
|
||||
room: Room;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
onSearchClick?: () => void;
|
||||
}
|
||||
|
||||
type Props = XOR<RoomlessProps, RoomProps>;
|
||||
|
@ -293,6 +294,7 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
onClose={this.onClose}
|
||||
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||
permalinkCreator={this.props.permalinkCreator!}
|
||||
onSearchClick={this.props.onSearchClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2470,6 +2470,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
resizeNotifier={this.props.resizeNotifier}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
onSearchClick={this.onSearchClick}
|
||||
/>
|
||||
) : undefined;
|
||||
|
||||
|
|
|
@ -684,7 +684,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
|
|||
<br />
|
||||
<input
|
||||
type="submit"
|
||||
value={_t("Submit")}
|
||||
value={_t("action|submit")}
|
||||
className={submitClasses}
|
||||
disabled={!enableSubmit}
|
||||
/>
|
||||
|
|
|
@ -99,7 +99,7 @@ const BaseAvatar: React.FC<IProps> = (props) => {
|
|||
const {
|
||||
name,
|
||||
idName,
|
||||
title = "",
|
||||
title,
|
||||
url,
|
||||
urls,
|
||||
size = "40px",
|
||||
|
|
|
@ -68,7 +68,7 @@ export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
|
|||
)}
|
||||
<div className="mx_AppDownloadDialog_mobile">
|
||||
<div className="mx_AppDownloadDialog_app">
|
||||
<Heading size="3">{_t("iOS")}</Heading>
|
||||
<Heading size="3">{_t("common|ios")}</Heading>
|
||||
<QRCode data={urlAppStore} margin={0} width={172} />
|
||||
<div className="mx_AppDownloadDialog_info">
|
||||
{_t("%(qrCode)s or %(appLinks)s", {
|
||||
|
@ -89,7 +89,7 @@ export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mx_AppDownloadDialog_app">
|
||||
<Heading size="3">{_t("Android")}</Heading>
|
||||
<Heading size="3">{_t("common|android")}</Heading>
|
||||
<QRCode data={urlAndroid} margin={0} width={172} />
|
||||
<div className="mx_AppDownloadDialog_info">
|
||||
{_t("%(qrCode)s or %(appLinks)s", {
|
||||
|
|
|
@ -410,7 +410,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
|
|||
</div>
|
||||
) : (
|
||||
<DialogButtons
|
||||
primaryButton={_t("Export")}
|
||||
primaryButton={_t("action|export")}
|
||||
onPrimaryButtonClick={onExportClick}
|
||||
onCancel={() => onFinished(false)}
|
||||
/>
|
||||
|
|
|
@ -1313,7 +1313,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
|||
);
|
||||
}
|
||||
|
||||
buttonText = _t("Go");
|
||||
buttonText = _t("action|go");
|
||||
goButtonFn = this.checkProfileAndStartDm;
|
||||
extraSection = (
|
||||
<div className="mx_InviteDialog_section_hidden_suggestions_disclaimer">
|
||||
|
|
|
@ -428,7 +428,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
{ignoreUserCheckbox}
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Send report")}
|
||||
primaryButton={_t("action|send_report")}
|
||||
onPrimaryButtonClick={this.onSubmit}
|
||||
focus={true}
|
||||
onCancel={this.onCancel}
|
||||
|
@ -467,7 +467,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
{ignoreUserCheckbox}
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Send report")}
|
||||
primaryButton={_t("action|send_report")}
|
||||
onPrimaryButtonClick={this.onSubmit}
|
||||
focus={true}
|
||||
onCancel={this.onCancel}
|
||||
|
|
|
@ -78,7 +78,7 @@ export default class SessionRestoreErrorDialog extends React.Component<IProps> {
|
|||
} else {
|
||||
dialogButtons = (
|
||||
<DialogButtons
|
||||
primaryButton={_t("Refresh")}
|
||||
primaryButton={_t("action|refresh")}
|
||||
onPrimaryButtonClick={this.onRefreshClick}
|
||||
focus={true}
|
||||
hasCancel={false}
|
||||
|
|
|
@ -98,7 +98,7 @@ export const AccountDataExplorer: React.FC<IDevtoolsProps> = ({ onBack, setTool
|
|||
<BaseAccountDataExplorer
|
||||
events={cli.store.accountData}
|
||||
Editor={AccountDataEventEditor}
|
||||
actionLabel={_t("Send custom account data event")}
|
||||
actionLabel={_t("devtools|send_custom_account_data_event")}
|
||||
onBack={onBack}
|
||||
setTool={setTool}
|
||||
/>
|
||||
|
@ -112,7 +112,7 @@ export const RoomAccountDataExplorer: React.FC<IDevtoolsProps> = ({ onBack, setT
|
|||
<BaseAccountDataExplorer
|
||||
events={context.room.accountData}
|
||||
Editor={RoomAccountDataEventEditor}
|
||||
actionLabel={_t("Send custom room account data event")}
|
||||
actionLabel={_t("devtools|send_custom_room_account_data_event")}
|
||||
onBack={onBack}
|
||||
setTool={setTool}
|
||||
/>
|
||||
|
|
|
@ -43,13 +43,13 @@ interface IFieldDef {
|
|||
|
||||
export const eventTypeField = (defaultValue?: string): IFieldDef => ({
|
||||
id: "eventType",
|
||||
label: _td("Event Type"),
|
||||
label: _td("devtools|event_type"),
|
||||
default: defaultValue,
|
||||
});
|
||||
|
||||
export const stateKeyField = (defaultValue?: string): IFieldDef => ({
|
||||
id: "stateKey",
|
||||
label: _td("State Key"),
|
||||
label: _td("devtools|state_key"),
|
||||
default: defaultValue,
|
||||
});
|
||||
|
||||
|
@ -69,7 +69,7 @@ const validateEventContent = withValidation<any, Error | undefined>({
|
|||
if (!value) return true;
|
||||
return !error;
|
||||
},
|
||||
invalid: (error) => _t("Doesn't look like valid JSON.") + " " + error,
|
||||
invalid: (error) => _t("devtools|invalid_json") + " " + error,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -111,9 +111,9 @@ export const EventEditor: React.FC<IEventEditorProps> = ({ fieldDefs, defaultCon
|
|||
const json = JSON.parse(content);
|
||||
await onSend(fieldData, json);
|
||||
} catch (e) {
|
||||
return _t("Failed to send event!") + (e instanceof Error ? ` (${e.message})` : "");
|
||||
return _t("devtools|failed_to_send") + (e instanceof Error ? ` (${e.message})` : "");
|
||||
}
|
||||
return _t("Event sent!");
|
||||
return _t("devtools|event_sent");
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -122,7 +122,7 @@ export const EventEditor: React.FC<IEventEditorProps> = ({ fieldDefs, defaultCon
|
|||
|
||||
<Field
|
||||
id="evContent"
|
||||
label={_t("Event Content")}
|
||||
label={_t("devtools|event_content")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
autoComplete="off"
|
||||
|
|
|
@ -33,27 +33,29 @@ function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Elemen
|
|||
return (
|
||||
<>
|
||||
<li>
|
||||
{_t("User read up to: ")}
|
||||
<strong>{target.getReadReceiptForUserId(userId)?.eventId ?? _t("No receipt found")}</strong>
|
||||
{_t("devtools|user_read_up_to")}
|
||||
<strong>{target.getReadReceiptForUserId(userId)?.eventId ?? _t("devtools|no_receipt_found")}</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("User read up to (ignoreSynthetic): ")}
|
||||
<strong>{target.getReadReceiptForUserId(userId, true)?.eventId ?? _t("No receipt found")}</strong>
|
||||
{_t("devtools|user_read_up_to_ignore_synthetic")}
|
||||
<strong>
|
||||
{target.getReadReceiptForUserId(userId, true)?.eventId ?? _t("devtools|no_receipt_found")}
|
||||
</strong>
|
||||
</li>
|
||||
{hasPrivate && (
|
||||
<>
|
||||
<li>
|
||||
{_t("User read up to (m.read.private): ")}
|
||||
{_t("devtools|user_read_up_to_private")}
|
||||
<strong>
|
||||
{target.getReadReceiptForUserId(userId, false, ReceiptType.ReadPrivate)?.eventId ??
|
||||
_t("No receipt found")}
|
||||
_t("devtools|no_receipt_found")}
|
||||
</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("User read up to (m.read.private;ignoreSynthetic): ")}
|
||||
{_t("devtools|user_read_up_to_private_ignore_synthetic")}
|
||||
<strong>
|
||||
{target.getReadReceiptForUserId(userId, true, ReceiptType.ReadPrivate)?.eventId ??
|
||||
_t("No receipt found")}
|
||||
_t("devtools|no_receipt_found")}
|
||||
</strong>
|
||||
</li>
|
||||
</>
|
||||
|
@ -72,12 +74,12 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
return (
|
||||
<BaseTool onBack={onBack}>
|
||||
<section>
|
||||
<h2>{_t("Room status")}</h2>
|
||||
<h2>{_t("devtools|room_status")}</h2>
|
||||
<ul>
|
||||
<li>
|
||||
{count > 0
|
||||
? _t(
|
||||
"Room unread status: <strong>%(status)s</strong>, count: <strong>%(count)s</strong>",
|
||||
"devtools|room_unread_status_count",
|
||||
{
|
||||
status: humanReadableNotificationColor(color),
|
||||
count,
|
||||
|
@ -87,7 +89,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
},
|
||||
)
|
||||
: _t(
|
||||
"Room unread status: <strong>%(status)s</strong>",
|
||||
"devtools|room_unread_status",
|
||||
{
|
||||
status: humanReadableNotificationColor(color),
|
||||
},
|
||||
|
@ -98,7 +100,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
</li>
|
||||
<li>
|
||||
{_t(
|
||||
"Notification state is <strong>%(notificationState)s</strong>",
|
||||
"devtools|notification_state",
|
||||
{
|
||||
notificationState,
|
||||
},
|
||||
|
@ -110,8 +112,8 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
<li>
|
||||
{_t(
|
||||
cli.isRoomEncrypted(room.roomId!)
|
||||
? _td("Room is <strong>encrypted ✅</strong>")
|
||||
: _td("Room is <strong>not encrypted 🚨</strong>"),
|
||||
? _td("devtools|room_encrypted")
|
||||
: _td("devtools|room_not_encrypted"),
|
||||
{},
|
||||
{
|
||||
strong: (sub) => <strong>{sub}</strong>,
|
||||
|
@ -122,33 +124,36 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<h2>{_t("Main timeline")}</h2>
|
||||
<h2>{_t("devtools|main_timeline")}</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
{_t("Total: ")} {room.getRoomUnreadNotificationCount(NotificationCountType.Total)}
|
||||
{_t("devtools|room_notifications_total")}{" "}
|
||||
{room.getRoomUnreadNotificationCount(NotificationCountType.Total)}
|
||||
</li>
|
||||
<li>
|
||||
{_t("Highlight: ")} {room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)}
|
||||
{_t("devtools|room_notifications_highlight")}{" "}
|
||||
{room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)}
|
||||
</li>
|
||||
<li>
|
||||
{_t("Dot: ")} {doesRoomOrThreadHaveUnreadMessages(room) + ""}
|
||||
{_t("devtools|room_notifications_dot")} {doesRoomOrThreadHaveUnreadMessages(room) + ""}
|
||||
</li>
|
||||
{roomHasUnread(room) && (
|
||||
<>
|
||||
<UserReadUpTo target={room} />
|
||||
<li>
|
||||
{_t("Last event:")}
|
||||
{_t("devtools|room_notifications_last_event")}
|
||||
<ul>
|
||||
<li>
|
||||
{_t("ID: ")} <strong>{room.timeline[room.timeline.length - 1].getId()}</strong>
|
||||
{_t("devtools|id")}{" "}
|
||||
<strong>{room.timeline[room.timeline.length - 1].getId()}</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("Type: ")}{" "}
|
||||
{_t("devtools|room_notifications_type")}{" "}
|
||||
<strong>{room.timeline[room.timeline.length - 1].getType()}</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("Sender: ")}{" "}
|
||||
{_t("devtools|room_notifications_sender")}{" "}
|
||||
<strong>{room.timeline[room.timeline.length - 1].getSender()}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -159,17 +164,17 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<h2>{_t("Threads timeline")}</h2>
|
||||
<h2>{_t("devtools|threads_timeline")}</h2>
|
||||
<ul>
|
||||
{room
|
||||
.getThreads()
|
||||
.filter((thread) => threadHasUnread(thread))
|
||||
.map((thread) => (
|
||||
<li key={thread.id}>
|
||||
{_t("Thread Id: ")} {thread.id}
|
||||
{_t("devtools|room_notifications_thread_id")} {thread.id}
|
||||
<ul>
|
||||
<li>
|
||||
{_t("Total: ")}
|
||||
{_t("devtools|room_notifications_total")}
|
||||
<strong>
|
||||
{room.getThreadUnreadNotificationCount(
|
||||
thread.id,
|
||||
|
@ -178,7 +183,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("Highlight: ")}
|
||||
{_t("devtools|room_notifications_highlight")}
|
||||
<strong>
|
||||
{room.getThreadUnreadNotificationCount(
|
||||
thread.id,
|
||||
|
@ -187,20 +192,23 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
|
|||
</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("Dot: ")} <strong>{doesRoomOrThreadHaveUnreadMessages(thread) + ""}</strong>
|
||||
{_t("devtools|room_notifications_dot")}{" "}
|
||||
<strong>{doesRoomOrThreadHaveUnreadMessages(thread) + ""}</strong>
|
||||
</li>
|
||||
<UserReadUpTo target={thread} />
|
||||
<li>
|
||||
{_t("Last event:")}
|
||||
{_t("devtools|room_notifications_last_event")}
|
||||
<ul>
|
||||
<li>
|
||||
{_t("ID: ")} <strong>{thread.lastReply()?.getId()}</strong>
|
||||
{_t("devtools|id")} <strong>{thread.lastReply()?.getId()}</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("Type: ")} <strong>{thread.lastReply()?.getType()}</strong>
|
||||
{_t("devtools|room_notifications_type")}{" "}
|
||||
<strong>{thread.lastReply()?.getType()}</strong>
|
||||
</li>
|
||||
<li>
|
||||
{_t("Sender: ")} <strong>{thread.lastReply()?.getSender()}</strong>
|
||||
{_t("devtools|room_notifications_sender")}{" "}
|
||||
<strong>{thread.lastReply()?.getSender()}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
|
@ -97,7 +97,7 @@ const StateEventButton: React.FC<StateEventButtonProps> = ({ label, onClick }) =
|
|||
|
||||
let content = label;
|
||||
if (!trimmed) {
|
||||
content = label.length > 0 ? _t("<%(count)s spaces>", { count: label.length }) : _t("<empty string>");
|
||||
content = label.length > 0 ? _t("devtools|spaces", { count: label.length }) : _t("devtools|empty_string");
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -150,7 +150,7 @@ const RoomStateExplorerEventType: React.FC<IEventTypeProps> = ({ eventType, onBa
|
|||
const onHistoryClick = (): void => {
|
||||
setHistory(true);
|
||||
};
|
||||
const extraButton = <button onClick={onHistoryClick}>{_t("See history")}</button>;
|
||||
const extraButton = <button onClick={onHistoryClick}>{_t("devtools|see_history")}</button>;
|
||||
return <EventViewer mxEvent={event} onBack={_onBack} Editor={StateEventEditor} extraButton={extraButton} />;
|
||||
}
|
||||
|
||||
|
@ -180,11 +180,11 @@ export const RoomStateExplorer: React.FC<IDevtoolsProps> = ({ onBack, setTool })
|
|||
}
|
||||
|
||||
const onAction = async (): Promise<void> => {
|
||||
setTool(_t("Send custom state event"), StateEventEditor);
|
||||
setTool(_t("devtools|send_custom_state_event"), StateEventEditor);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseTool onBack={onBack} actionLabel={_t("Send custom state event")} onAction={onAction}>
|
||||
<BaseTool onBack={onBack} actionLabel={_t("devtools|send_custom_state_event")} onAction={onAction}>
|
||||
<FilteredList query={query} onChange={setQuery}>
|
||||
{Array.from(events.keys()).map((eventType) => (
|
||||
<StateEventButton key={eventType} label={eventType} onClick={() => setEventType(eventType)} />
|
||||
|
|
|
@ -73,25 +73,25 @@ const ServerInfo: React.FC<IDevtoolsProps> = ({ onBack }) => {
|
|||
} else {
|
||||
body = (
|
||||
<>
|
||||
<h4>{_t("Capabilities")}</h4>
|
||||
<h4>{_t("common|capabilities")}</h4>
|
||||
{capabilities !== FAILED_TO_LOAD ? (
|
||||
<SyntaxHighlight language="json" children={JSON.stringify(capabilities, null, 4)} />
|
||||
) : (
|
||||
<div>{_t("Failed to load.")}</div>
|
||||
<div>{_t("devtools|failed_to_load")}</div>
|
||||
)}
|
||||
|
||||
<h4>{_t("Client Versions")}</h4>
|
||||
<h4>{_t("devtools|client_versions")}</h4>
|
||||
{clientVersions !== FAILED_TO_LOAD ? (
|
||||
<SyntaxHighlight language="json" children={JSON.stringify(clientVersions, null, 4)} />
|
||||
) : (
|
||||
<div>{_t("Failed to load.")}</div>
|
||||
<div>{_t("devtools|failed_to_load")}</div>
|
||||
)}
|
||||
|
||||
<h4>{_t("Server Versions")}</h4>
|
||||
<h4>{_t("devtools|server_versions")}</h4>
|
||||
{serverVersions !== FAILED_TO_LOAD ? (
|
||||
<SyntaxHighlight language="json" children={JSON.stringify(serverVersions, null, 4)} />
|
||||
) : (
|
||||
<div>{_t("Failed to load.")}</div>
|
||||
<div>{_t("devtools|failed_to_load")}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -39,8 +39,8 @@ const ServersInRoom: React.FC<IDevtoolsProps> = ({ onBack }) => {
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{_t("Server")}</th>
|
||||
<th>{_t("Number of users")}</th>
|
||||
<th>{_t("common|server")}</th>
|
||||
<th>{_t("devtools|number_of_users")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -125,22 +125,22 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
|
|||
}
|
||||
onBack();
|
||||
} catch (e) {
|
||||
return _t("Failed to save settings.") + (e instanceof Error ? ` (${e.message})` : "");
|
||||
return _t("devtools|failed_to_save") + (e instanceof Error ? ` (${e.message})` : "");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseTool onBack={onBack} actionLabel={_t("Save setting values")} onAction={onSave}>
|
||||
<BaseTool onBack={onBack} actionLabel={_t("devtools|save_setting_values")} onAction={onSave}>
|
||||
<h3>
|
||||
{_t("Setting:")} <code>{setting}</code>
|
||||
{_t("devtools|setting_colon")} <code>{setting}</code>
|
||||
</h3>
|
||||
|
||||
<div className="mx_DevTools_SettingsExplorer_warning">
|
||||
<b>{_t("Caution:")}</b> {_t("This UI does NOT check the types of the values. Use at your own risk.")}
|
||||
<b>{_t("devtools|caution_colon")}</b> {_t("devtools|use_at_own_risk")}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{_t("Setting definition:")}
|
||||
{_t("devtools|setting_definition")}
|
||||
<pre>
|
||||
<code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
|
||||
</pre>
|
||||
|
@ -150,9 +150,9 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{_t("Level")}</th>
|
||||
<th>{_t("Settable at global")}</th>
|
||||
<th>{_t("Settable at room")}</th>
|
||||
<th>{_t("devtools|level")}</th>
|
||||
<th>{_t("devtools|settable_global")}</th>
|
||||
<th>{_t("devtools|settable_room")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -172,7 +172,7 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
|
|||
<div>
|
||||
<Field
|
||||
id="valExpl"
|
||||
label={_t("Values at explicit levels")}
|
||||
label={_t("devtools|values_explicit")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
element="textarea"
|
||||
|
@ -185,7 +185,7 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
|
|||
<div>
|
||||
<Field
|
||||
id="valExpl"
|
||||
label={_t("Values at explicit levels in this room")}
|
||||
label={_t("devtools|values_explicit_room")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
element="textarea"
|
||||
|
@ -207,37 +207,37 @@ const ViewSetting: React.FC<IViewSettingProps> = ({ setting, onEdit, onBack }) =
|
|||
const context = useContext(DevtoolsContext);
|
||||
|
||||
return (
|
||||
<BaseTool onBack={onBack} actionLabel={_t("Edit values")} onAction={onEdit}>
|
||||
<BaseTool onBack={onBack} actionLabel={_t("devtools|edit_values")} onAction={onEdit}>
|
||||
<h3>
|
||||
{_t("Setting:")} <code>{setting}</code>
|
||||
{_t("devtools|setting_colon")} <code>{setting}</code>
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
{_t("Setting definition:")}
|
||||
{_t("devtools|setting_definition")}
|
||||
<pre>
|
||||
<code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{_t("Value:")}
|
||||
{_t("devtools|value_colon")}
|
||||
<code>{renderSettingValue(SettingsStore.getValue(setting))}</code>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{_t("Value in this room:")}
|
||||
{_t("devtools|value_this_room_colon")}
|
||||
<code>{renderSettingValue(SettingsStore.getValue(setting, context.room.roomId))}</code>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{_t("Values at explicit levels:")}
|
||||
{_t("devtools|values_explicit_colon")}
|
||||
<pre>
|
||||
<code>{renderExplicitSettingValues(setting)}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{_t("Values at explicit levels in this room:")}
|
||||
{_t("devtools|values_explicit_this_room_colon")}
|
||||
<pre>
|
||||
<code>{renderExplicitSettingValues(setting, context.room.roomId)}</code>
|
||||
</pre>
|
||||
|
@ -289,9 +289,9 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{_t("Setting ID")}</th>
|
||||
<th>{_t("Value")}</th>
|
||||
<th>{_t("Value in this room")}</th>
|
||||
<th>{_t("devtools|setting_id")}</th>
|
||||
<th>{_t("devtools|value")}</th>
|
||||
<th>{_t("devtools|value_in_this_room")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -306,7 +306,7 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
|
|||
<code>{i}</code>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
alt={_t("Edit setting")}
|
||||
alt={_t("devtools|edit_setting")}
|
||||
onClick={() => onEdit(i)}
|
||||
className="mx_DevTools_SettingsExplorer_edit"
|
||||
>
|
||||
|
|
|
@ -28,11 +28,11 @@ import { Tool } from "../DevtoolsDialog";
|
|||
|
||||
const PHASE_MAP: Record<Phase, TranslationKey> = {
|
||||
[Phase.Unsent]: _td("Unsent"),
|
||||
[Phase.Requested]: _td("Requested"),
|
||||
[Phase.Ready]: _td("Ready"),
|
||||
[Phase.Requested]: _td("devtools|phase_requested"),
|
||||
[Phase.Ready]: _td("devtools|phase_ready"),
|
||||
[Phase.Done]: _td("action|done"),
|
||||
[Phase.Started]: _td("Started"),
|
||||
[Phase.Cancelled]: _td("Cancelled"),
|
||||
[Phase.Started]: _td("devtools|phase_started"),
|
||||
[Phase.Cancelled]: _td("devtools|phase_cancelled"),
|
||||
};
|
||||
|
||||
const VerificationRequestExplorer: React.FC<{
|
||||
|
@ -62,17 +62,17 @@ const VerificationRequestExplorer: React.FC<{
|
|||
return (
|
||||
<div className="mx_DevTools_VerificationRequest">
|
||||
<dl>
|
||||
<dt>{_t("Transaction")}</dt>
|
||||
<dt>{_t("devtools|phase_transaction")}</dt>
|
||||
<dd>{txnId}</dd>
|
||||
<dt>{_t("Phase")}</dt>
|
||||
<dt>{_t("devtools|phase")}</dt>
|
||||
<dd>{PHASE_MAP[request.phase] ? _t(PHASE_MAP[request.phase]) : request.phase}</dd>
|
||||
<dt>{_t("Timeout")}</dt>
|
||||
<dt>{_t("devtools|timeout")}</dt>
|
||||
<dd>{Math.floor(timeout / 1000)}</dd>
|
||||
<dt>{_t("Methods")}</dt>
|
||||
<dt>{_t("devtools|methods")}</dt>
|
||||
<dd>{request.methods && request.methods.join(", ")}</dd>
|
||||
<dt>{_t("Requester")}</dt>
|
||||
<dt>{_t("devtools|requester")}</dt>
|
||||
<dd>{request.requestingUserId}</dd>
|
||||
<dt>{_t("Observe only")}</dt>
|
||||
<dt>{_t("devtools|observe_only")}</dt>
|
||||
<dd>{JSON.stringify(request.observeOnly)}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
@ -97,7 +97,7 @@ const VerificationExplorer: Tool = ({ onBack }: IDevtoolsProps) => {
|
|||
.map(([txnId, request]) => (
|
||||
<VerificationRequestExplorer txnId={txnId} request={request} key={txnId} />
|
||||
))}
|
||||
{requests.size < 1 && _t("No verification requests found")}
|
||||
{requests.size < 1 && _t("devtools|no_verification_requests_found")}
|
||||
</BaseTool>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -51,7 +51,7 @@ const WidgetExplorer: React.FC<IDevtoolsProps> = ({ onBack }) => {
|
|||
const event = allState.find((ev) => ev.getId() === widget.eventId);
|
||||
if (!event) {
|
||||
// "should never happen"
|
||||
return <BaseTool onBack={onBack}>{_t("There was an error finding this widget.")}</BaseTool>;
|
||||
return <BaseTool onBack={onBack}>{_t("devtools|failed_to_find_widget")}</BaseTool>;
|
||||
}
|
||||
|
||||
return <StateEventEditor mxEvent={event} onBack={onBack} />;
|
||||
|
|
|
@ -1038,7 +1038,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
<h4>
|
||||
<span id="mx_SpotlightDialog_section_recentSearches">{_t("Recent searches")}</span>
|
||||
<AccessibleButton kind="link" onClick={clearRecentSearches}>
|
||||
{_t("Clear")}
|
||||
{_t("action|clear")}
|
||||
</AccessibleButton>
|
||||
</h4>
|
||||
<div>
|
||||
|
|
|
@ -155,7 +155,7 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
|
|||
options: [
|
||||
{
|
||||
key: { roomServer, instanceId: undefined },
|
||||
label: _t("Matrix"),
|
||||
label: _t("common|matrix"),
|
||||
},
|
||||
...(roomServer === homeServer && protocols
|
||||
? Object.values(protocols)
|
||||
|
|
|
@ -737,7 +737,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
<AccessibleButton
|
||||
key="toggleMaximised"
|
||||
className="mx_AppTileMenuBar_widgets_button"
|
||||
title={isMaximised ? _t("Un-maximise") : _t("Maximise")}
|
||||
title={isMaximised ? _t("Un-maximise") : _t("action|maximise")}
|
||||
onClick={this.onToggleMaximisedClick}
|
||||
>
|
||||
{isMaximised ? (
|
||||
|
@ -752,7 +752,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
<AccessibleButton
|
||||
key="minimise"
|
||||
className="mx_AppTileMenuBar_widgets_button"
|
||||
title={_t("Minimise")}
|
||||
title={_t("action|minimise")}
|
||||
onClick={this.onMinimiseClicked}
|
||||
>
|
||||
<MinimiseIcon className="mx_Icon mx_Icon_12" />
|
||||
|
|
|
@ -270,12 +270,12 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
|||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Last week")}
|
||||
label={_t("time|last_week")}
|
||||
onClick={this.onLastWeekClicked}
|
||||
data-testid="jump-to-date-last-week"
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Last month")}
|
||||
label={_t("time|last_month")}
|
||||
onClick={this.onLastMonthClicked}
|
||||
data-testid="jump-to-date-last-month"
|
||||
/>
|
||||
|
|
|
@ -63,7 +63,7 @@ const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {
|
|||
className="mx_JumpToDatePicker_submitButton"
|
||||
onClick={onJumpToDateSubmit}
|
||||
>
|
||||
{_t("Go")}
|
||||
{_t("action|go")}
|
||||
</RovingAccessibleButton>
|
||||
</form>
|
||||
);
|
||||
|
|
|
@ -58,6 +58,7 @@ interface IProps {
|
|||
room: Room;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
onClose(): void;
|
||||
onSearchClick?: () => void;
|
||||
}
|
||||
|
||||
interface IAppsSectionProps {
|
||||
|
@ -162,7 +163,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
|
|||
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Center);
|
||||
};
|
||||
|
||||
const maximiseTitle = isMaximised ? _t("action|close") : _t("Maximise");
|
||||
const maximiseTitle = isMaximised ? _t("action|close") : _t("action|maximise");
|
||||
|
||||
let openTitle = "";
|
||||
if (isPinned) {
|
||||
|
@ -272,7 +273,7 @@ const onRoomSettingsClick = (ev: ButtonEvent): void => {
|
|||
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
|
||||
};
|
||||
|
||||
const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose }) => {
|
||||
const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, onSearchClick }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const onShareRoomClick = (): void => {
|
||||
|
@ -309,7 +310,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose })
|
|||
<div className="mx_RoomSummaryCard_avatar" role="presentation">
|
||||
<RoomAvatar room={room} size="54px" viewAvatarOnClick />
|
||||
<TextWithTooltip
|
||||
tooltip={isRoomEncrypted ? _t("Encrypted") : _t("Not encrypted")}
|
||||
tooltip={isRoomEncrypted ? _t("common|encrypted") : _t("Not encrypted")}
|
||||
class={classNames("mx_RoomSummaryCard_e2ee", {
|
||||
mx_RoomSummaryCard_e2ee_normal: isRoomEncrypted,
|
||||
mx_RoomSummaryCard_e2ee_warning: isRoomEncrypted && e2eStatus === E2EStatus.Warning,
|
||||
|
@ -342,6 +343,14 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose })
|
|||
{_t("common|people")}
|
||||
<span className="mx_BaseCard_Button_sublabel">{memberCount}</span>
|
||||
</Button>
|
||||
<Button
|
||||
className="mx_RoomSummaryCard_icon_search"
|
||||
onClick={() => {
|
||||
onSearchClick?.();
|
||||
}}
|
||||
>
|
||||
{_t("Search")}
|
||||
</Button>
|
||||
{!isVideoRoom && (
|
||||
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
|
||||
{_t("Files")}
|
||||
|
|
|
@ -206,7 +206,7 @@ export function DeviceItem({ userId, device }: { userId: string; device: IDevice
|
|||
}
|
||||
|
||||
let trustedLabel: string | undefined;
|
||||
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
||||
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("common|trusted") : _t("common|not_trusted");
|
||||
|
||||
if (isVerified === undefined) {
|
||||
// we're still deciding if the device is verified
|
||||
|
@ -443,7 +443,7 @@ export const UserOptionsSection: React.FC<{
|
|||
|
||||
insertPillButton = (
|
||||
<AccessibleButton kind="link" onClick={onInsertPillButton} className="mx_UserInfo_field">
|
||||
{_t("Mention")}
|
||||
{_t("action|mention")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -981,9 +981,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||
mx_EventTile_lastInSection: this.props.lastInSection,
|
||||
mx_EventTile_contextual: this.props.contextual,
|
||||
mx_EventTile_actionBarFocused: this.state.actionBarFocused,
|
||||
mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2EState.Verified,
|
||||
mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2EState.Warning,
|
||||
mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2EState.Unknown,
|
||||
mx_EventTile_bad: isEncryptionFailure,
|
||||
mx_EventTile_emote: msgtype === MsgType.Emote,
|
||||
mx_EventTile_noSender: this.props.hideSender,
|
||||
|
|
|
@ -36,6 +36,7 @@ import RoomTopic from "../elements/RoomTopic";
|
|||
import RoomName from "../elements/RoomName";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||
import { RoomKnocksBar } from "./RoomKnocksBar";
|
||||
import { SearchScope } from "./SearchBar";
|
||||
import { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import RoomContextMenu from "../context_menus/RoomContextMenu";
|
||||
|
@ -820,6 +821,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
</div>
|
||||
{!isVideoRoom && <RoomCallBanner roomId={this.props.room.roomId} />}
|
||||
<RoomLiveShareWarning roomId={this.props.room.roomId} />
|
||||
<RoomKnocksBar room={this.props.room} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,38 +14,41 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { Body as BodyText, IconButton } from "@vector-im/compound-web";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Body as BodyText, IconButton, Tooltip } from "@vector-im/compound-web";
|
||||
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call.svg";
|
||||
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
|
||||
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
|
||||
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
|
||||
import { Icon as VerifiedIcon } from "@vector-im/compound-design-tokens/icons/verified.svg";
|
||||
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
|
||||
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useRoomName } from "../../../hooks/useRoomName";
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { useTopic } from "../../../hooks/room/useTopic";
|
||||
import { useAccountData } from "../../../hooks/useAccountData";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMembers";
|
||||
import { _t, getCurrentLanguage } from "../../../languageHandler";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import { Box } from "../../utils/Box";
|
||||
import { useRoomCallStatus } from "../../../hooks/room/useRoomCallStatus";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
|
||||
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import { placeCall } from "../../../utils/room/placeCall";
|
||||
import { useEncryptionStatus } from "../../../hooks/useEncryptionStatus";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import FacePile from "../elements/FacePile";
|
||||
import { setPhase } from "../../../utils/room/setPhase";
|
||||
import { useRoomState } from "../../../hooks/useRoomState";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import { formatCount } from "../../../utils/FormattingUtils";
|
||||
|
||||
/**
|
||||
* A helper to transform a notification color to the what the Compound Icon Button
|
||||
|
@ -66,19 +69,10 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
|
||||
const roomName = useRoomName(room);
|
||||
const roomTopic = useTopic(room);
|
||||
const roomState = useRoomState(room);
|
||||
|
||||
const members = useRoomMembers(room);
|
||||
const memberCount = useRoomMemberCount(room);
|
||||
|
||||
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
|
||||
const isDirectMessage = useMemo(() => {
|
||||
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
|
||||
if (dmRoomList.includes(room?.roomId ?? "")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, [directRoomsList, room?.roomId]);
|
||||
const members = useRoomMembers(room, 2500);
|
||||
const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
|
||||
|
||||
const { voiceCallDisabledReason, voiceCallType, videoCallDisabledReason, videoCallType } = useRoomCallStatus(room);
|
||||
|
||||
|
@ -91,37 +85,21 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
return SdkConfig.get("element_call").use_exclusively && groupCallsEnabled;
|
||||
}, [groupCallsEnabled]);
|
||||
|
||||
const placeCall = useCallback(
|
||||
async (callType: CallType, platformCallType: typeof voiceCallType) => {
|
||||
switch (platformCallType) {
|
||||
case "legacy_or_jitsi":
|
||||
await LegacyCallHandler.instance.placeCall(room.roomId, callType);
|
||||
break;
|
||||
// TODO: Remove the jitsi_or_element_call case and
|
||||
// use the commented code below
|
||||
case "element_call":
|
||||
case "jitsi_or_element_call":
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
view_call: true,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
// case "jitsi_or_element_call":
|
||||
// TODO: Open dropdown menu to choice between
|
||||
// EC and Jitsi. Waiting on Compound's dropdown
|
||||
// component
|
||||
// break;
|
||||
}
|
||||
},
|
||||
[room.roomId],
|
||||
);
|
||||
|
||||
const threadNotifications = useRoomThreadNotifications(room);
|
||||
const globalNotificationState = useGlobalNotificationState();
|
||||
|
||||
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
|
||||
const [isDirectMessage, setDirectMessage] = useState(false);
|
||||
useEffect(() => {
|
||||
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
|
||||
if (dmRoomList.includes(room?.roomId ?? "")) {
|
||||
setDirectMessage(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [room, directRoomsList]);
|
||||
const e2eStatus = useEncryptionStatus(client, room);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="header"
|
||||
|
@ -132,7 +110,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
setPhase(RightPanelPhases.RoomSummary);
|
||||
}}
|
||||
>
|
||||
<DecoratedRoomAvatar room={room} size="40px" displayBadge={false} />
|
||||
<RoomAvatar room={room} size="40px" />
|
||||
<Box flex="1" className="mx_RoomHeader_info">
|
||||
<BodyText
|
||||
as="div"
|
||||
|
@ -142,8 +120,42 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
title={roomName}
|
||||
role="heading"
|
||||
aria-level={1}
|
||||
className="mx_RoomHeader_heading"
|
||||
>
|
||||
{roomName}
|
||||
|
||||
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
|
||||
<Tooltip label={_t("Public room")}>
|
||||
<PublicIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="text-secondary"
|
||||
aria-label={_t("Public room")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isDirectMessage && e2eStatus === E2EStatus.Verified && (
|
||||
<Tooltip label={_t("common|verified")}>
|
||||
<VerifiedIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="mx_Verified"
|
||||
aria-label={_t("common|verified")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
|
||||
<Tooltip label={_t("Untrusted")}>
|
||||
<ErrorIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="mx_Untrusted"
|
||||
aria-label={_t("Untrusted")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</BodyText>
|
||||
{roomTopic && (
|
||||
<BodyText as="div" size="sm" className="mx_RoomHeader_topic">
|
||||
|
@ -156,8 +168,8 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
<IconButton
|
||||
disabled={!!voiceCallDisabledReason}
|
||||
title={!voiceCallDisabledReason ? _t("Voice call") : voiceCallDisabledReason!}
|
||||
onClick={async () => {
|
||||
placeCall(CallType.Voice, voiceCallType);
|
||||
onClick={() => {
|
||||
placeCall(room, CallType.Voice, voiceCallType);
|
||||
}}
|
||||
>
|
||||
<VoiceCallIcon />
|
||||
|
@ -167,7 +179,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
disabled={!!videoCallDisabledReason}
|
||||
title={!videoCallDisabledReason ? _t("Video call") : videoCallDisabledReason!}
|
||||
onClick={() => {
|
||||
placeCall(CallType.Video, videoCallType);
|
||||
placeCall(room, CallType.Video, videoCallType);
|
||||
}}
|
||||
>
|
||||
<VideoCallIcon />
|
||||
|
@ -208,7 +220,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
size="20px"
|
||||
overflow={false}
|
||||
>
|
||||
{memberCount.toLocaleString(getCurrentLanguage())}
|
||||
{formatCount(memberCount)}
|
||||
</FacePile>
|
||||
</BodyText>
|
||||
)}
|
||||
|
|
159
src/components/views/rooms/RoomKnocksBar.tsx
Normal file
159
src/components/views/rooms/RoomKnocksBar.tsx
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
Copyright 2023 Nordeck IT + Consulting GmbH
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react";
|
||||
|
||||
import { Icon as CheckIcon } from "../../../../res/img/feather-customised/check.svg";
|
||||
import { Icon as XIcon } from "../../../../res/img/feather-customised/x.svg";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Heading from "../typography/Heading";
|
||||
|
||||
export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const knockMembers = useTypedEventEmitterState(
|
||||
room,
|
||||
RoomStateEvent.Members,
|
||||
useCallback(() => room.getMembersWithMembership("knock"), [room]),
|
||||
);
|
||||
const knockMembersCount = knockMembers.length;
|
||||
|
||||
if (room.getJoinRule() !== JoinRule.Knock || knockMembersCount === 0) return null;
|
||||
|
||||
const client = room.client;
|
||||
const userId = client.getUserId() || "";
|
||||
const canInvite = room.canInvite(userId);
|
||||
const member = room.getMember(userId);
|
||||
const state = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
const canKick = member && state ? state.hasSufficientPowerLevelFor("kick", member.powerLevel) : false;
|
||||
|
||||
if (!canInvite && !canKick) return null;
|
||||
|
||||
const onError = (error: MatrixError): void => {
|
||||
Modal.createDialog(ErrorDialog, { title: error.name, description: error.message });
|
||||
};
|
||||
|
||||
const handleApprove = (userId: string): void => {
|
||||
setDisabled(true);
|
||||
client
|
||||
.invite(room.roomId, userId)
|
||||
.catch(onError)
|
||||
.finally(() => setDisabled(false));
|
||||
};
|
||||
|
||||
const handleDeny = (userId: string): void => {
|
||||
setDisabled(true);
|
||||
client
|
||||
.kick(room.roomId, userId)
|
||||
.catch(onError)
|
||||
.finally(() => setDisabled(false));
|
||||
};
|
||||
|
||||
const handleOpenRoomSettings = (): void =>
|
||||
dis.dispatch({ action: "open_room_settings", room_id: room.roomId, initial_tab_id: RoomSettingsTab.People });
|
||||
|
||||
let buttons: ReactElement = (
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
kind="primary"
|
||||
onClick={handleOpenRoomSettings}
|
||||
title={_t("action|view")}
|
||||
>
|
||||
{_t("action|view")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
let names: string = knockMembers
|
||||
.slice(0, 2)
|
||||
.map((knockMember) => knockMember.name)
|
||||
.join(", ");
|
||||
let link: ReactNode = null;
|
||||
switch (knockMembersCount) {
|
||||
case 1: {
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canKick || disabled}
|
||||
kind="icon_primary_outline"
|
||||
onClick={() => handleDeny(knockMembers[0].userId)}
|
||||
title={_t("action|deny")}
|
||||
>
|
||||
<XIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canInvite || disabled}
|
||||
kind="icon_primary"
|
||||
onClick={() => handleApprove(knockMembers[0].userId)}
|
||||
title={_t("action|approve")}
|
||||
>
|
||||
<CheckIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
|
||||
link = (
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_link"
|
||||
element="a"
|
||||
kind="link_inline"
|
||||
onClick={handleOpenRoomSettings}
|
||||
>
|
||||
{_t("action|view_message")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
names = _t("%(names)s and %(name)s", { names: knockMembers[0].name, name: knockMembers[1].name });
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
names = _t("%(names)s and %(name)s", { names, name: knockMembers[2].name });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
names = _t("%(names)s and %(count)s others", { names, count: knockMembersCount - 2 });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomKnocksBar">
|
||||
{knockMembers.slice(0, 2).map((knockMember) => (
|
||||
<MemberAvatar
|
||||
className="mx_RoomKnocksBar_avatar"
|
||||
key={knockMember.userId}
|
||||
member={knockMember}
|
||||
size="32px"
|
||||
/>
|
||||
))}
|
||||
<div className="mx_RoomKnocksBar_content">
|
||||
<Heading size="4">{_t("%(count)s people asking to join", { count: knockMembersCount })}</Heading>
|
||||
<p className="mx_RoomKnocksBar_paragraph">
|
||||
{names}
|
||||
{link}
|
||||
</p>
|
||||
</div>
|
||||
{buttons}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -160,7 +160,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
|||
)}
|
||||
</SettingsSubsectionText>
|
||||
<AccessibleButton kind="primary" onClick={this.onManage}>
|
||||
{_t("Manage")}
|
||||
{_t("action|manage")}
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -73,18 +73,18 @@ const DeviceDetails: React.FC<Props> = ({
|
|||
},
|
||||
{
|
||||
id: "application",
|
||||
heading: _t("Application"),
|
||||
heading: _t("common|application"),
|
||||
values: [
|
||||
{ label: _t("common|name"), value: device.appName },
|
||||
{ label: _t("Version"), value: device.appVersion },
|
||||
{ label: _t("common|version"), value: device.appVersion },
|
||||
{ label: _t("URL"), value: device.url },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "device",
|
||||
heading: _t("Device"),
|
||||
heading: _t("common|device"),
|
||||
values: [
|
||||
{ label: _t("Model"), value: device.deviceModel },
|
||||
{ label: _t("common|model"), value: device.deviceModel },
|
||||
{ label: _t("Operating system"), value: device.deviceOperatingSystem },
|
||||
{ label: _t("Browser"), value: device.client },
|
||||
{ label: _t("IP address"), value: device.last_seen_ip },
|
||||
|
|
|
@ -63,7 +63,7 @@ const DeviceMetaDatum: React.FC<{ value: string | React.ReactNode; id: string }>
|
|||
export const DeviceMetaData: React.FC<Props> = ({ device }) => {
|
||||
const inactive = getInactiveMetadata(device);
|
||||
const lastActivity = device.last_seen_ts && `${_t("Last activity")} ${formatLastActivity(device.last_seen_ts)}`;
|
||||
const verificationStatus = device.isVerified ? _t("Verified") : _t("Unverified");
|
||||
const verificationStatus = device.isVerified ? _t("common|verified") : _t("common|unverified");
|
||||
// if device is inactive, don't display last activity or verificationStatus
|
||||
const metadata = inactive
|
||||
? [inactive, { id: "lastSeenIp", value: device.last_seen_ip }]
|
||||
|
|
|
@ -62,13 +62,13 @@ export const DeviceTypeIcon: React.FC<Props> = ({ isVerified, isSelected, device
|
|||
<VerifiedIcon
|
||||
className={classNames("mx_DeviceTypeIcon_verificationIcon", "verified")}
|
||||
role="img"
|
||||
aria-label={_t("Verified")}
|
||||
aria-label={_t("common|verified")}
|
||||
/>
|
||||
) : (
|
||||
<UnverifiedIcon
|
||||
className={classNames("mx_DeviceTypeIcon_verificationIcon", "unverified")}
|
||||
role="img"
|
||||
aria-label={_t("Unverified")}
|
||||
aria-label={_t("common|unverified")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -284,12 +284,12 @@ export const FilteredDeviceList = forwardRef(
|
|||
{ id: ALL_FILTER_ID, label: _t("All") },
|
||||
{
|
||||
id: DeviceSecurityVariation.Verified,
|
||||
label: _t("Verified"),
|
||||
label: _t("common|verified"),
|
||||
description: _t("Ready for secure messaging"),
|
||||
},
|
||||
{
|
||||
id: DeviceSecurityVariation.Unverified,
|
||||
label: _t("Unverified"),
|
||||
label: _t("common|unverified"),
|
||||
description: _t("Not ready for secure messaging"),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -82,7 +82,7 @@ const Knock: VFC<{
|
|||
|
||||
return (
|
||||
<div className="mx_PeopleRoomSettingsTab_knock">
|
||||
<MemberAvatar member={roomMember} size="42px" />
|
||||
<MemberAvatar className="mx_PeopleRoomSettingsTab_avatar" member={roomMember} size="42px" />
|
||||
<div className="mx_PeopleRoomSettingsTab_content">
|
||||
<span className="mx_PeopleRoomSettingsTab_name">{roomMember.name}</span>
|
||||
<Timestamp roomMember={roomMember} />
|
||||
|
|
|
@ -466,7 +466,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
<LabelledToggleSwitch
|
||||
value={isEncrypted}
|
||||
onChange={this.onEncryptionChange}
|
||||
label={_t("Encrypted")}
|
||||
label={_t("common|encrypted")}
|
||||
disabled={!canEnableEncryption}
|
||||
/>
|
||||
{isEncryptionForceDisabled && !isEncrypted && (
|
||||
|
|
|
@ -74,7 +74,7 @@ function UserOnboardingButtonInternal({ selected, minimized }: Props): JSX.Eleme
|
|||
<>
|
||||
<div className="mx_UserOnboardingButton_content">
|
||||
<Heading size="4" className="mx_Heading_h4">
|
||||
{_t("Welcome")}
|
||||
{_t("common|welcome")}
|
||||
</Heading>
|
||||
<AccessibleButton className="mx_UserOnboardingButton_close" onClick={onDismiss} />
|
||||
</div>
|
||||
|
|
|
@ -35,53 +35,38 @@ interface Props {
|
|||
|
||||
export function UserOnboardingHeader({ useCase }: Props): JSX.Element {
|
||||
let title: string;
|
||||
let description: string;
|
||||
let image;
|
||||
let description = _t("onboarding|free_e2ee_messaging_unlimited_voip", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
});
|
||||
let image: string;
|
||||
let actionLabel: string;
|
||||
|
||||
switch (useCase) {
|
||||
case UseCase.PersonalMessaging:
|
||||
title = _t("Secure messaging for friends and family");
|
||||
description = _t(
|
||||
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.",
|
||||
{
|
||||
brand: SdkConfig.get("brand"),
|
||||
},
|
||||
);
|
||||
title = _t("onboarding|personal_messaging_title");
|
||||
image = require("../../../../res/img/user-onboarding/PersonalMessaging.png");
|
||||
actionLabel = _t("Start your first chat");
|
||||
actionLabel = _t("onboarding|personal_messaging_action");
|
||||
break;
|
||||
case UseCase.WorkMessaging:
|
||||
title = _t("Secure messaging for work");
|
||||
description = _t(
|
||||
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.",
|
||||
{
|
||||
brand: SdkConfig.get("brand"),
|
||||
},
|
||||
);
|
||||
image = require("../../../../res/img/user-onboarding/WorkMessaging.png");
|
||||
actionLabel = _t("Find your co-workers");
|
||||
break;
|
||||
case UseCase.CommunityMessaging:
|
||||
title = _t("Community ownership");
|
||||
description = _t(
|
||||
"Keep ownership and control of community discussion.\nScale to support millions, with powerful moderation and interoperability.",
|
||||
);
|
||||
image = require("../../../../res/img/user-onboarding/CommunityMessaging.png");
|
||||
actionLabel = _t("Find your people");
|
||||
break;
|
||||
default:
|
||||
title = _t("Welcome to %(brand)s", {
|
||||
title = _t("onboarding|work_messaging_title");
|
||||
description = _t("onboarding|free_e2ee_messaging_unlimited_voip", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
});
|
||||
image = require("../../../../res/img/user-onboarding/WorkMessaging.png");
|
||||
actionLabel = _t("onboarding|work_messaging_action");
|
||||
break;
|
||||
case UseCase.CommunityMessaging:
|
||||
title = _t("onboarding|community_messaging_title");
|
||||
description = _t("onboarding|community_messaging_description");
|
||||
image = require("../../../../res/img/user-onboarding/CommunityMessaging.png");
|
||||
actionLabel = _t("onboarding|community_messaging_action");
|
||||
break;
|
||||
default:
|
||||
title = _t("onboarding|welcome_to_brand", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
});
|
||||
description = _t(
|
||||
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.",
|
||||
{
|
||||
brand: SdkConfig.get("brand"),
|
||||
},
|
||||
);
|
||||
image = require("../../../../res/img/user-onboarding/PersonalMessaging.png");
|
||||
actionLabel = _t("Start your first chat");
|
||||
actionLabel = _t("onboarding|personal_messaging_action");
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,13 +52,13 @@ export function UserOnboardingList({ tasks }: Props): JSX.Element {
|
|||
<div className="mx_UserOnboardingList_header">
|
||||
<Heading size="3" className="mx_UserOnboardingList_title">
|
||||
{waiting > 0
|
||||
? _t("Only %(count)s steps to go", {
|
||||
? _t("onboarding|only_n_steps_to_go", {
|
||||
count: waiting,
|
||||
})
|
||||
: _t("You did it!")}
|
||||
: _t("onboarding|you_did_it")}
|
||||
</Heading>
|
||||
<div className="mx_UserOnboardingList_hint">
|
||||
{_t("Complete these to get the most out of %(brand)s", {
|
||||
{_t("onboarding|complete_these", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
})}
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue