Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/17021

This commit is contained in:
Michael Telatynski 2021-05-06 08:05:14 +01:00
commit 90538c95aa
153 changed files with 2506 additions and 981 deletions

View file

@ -148,13 +148,15 @@ function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to %(groupId)s:",
{groupId},
),
description: errorList.join(", "),
});
'',
ErrorDialog,
{
title: _t(
"Failed to add the following rooms to %(groupId)s:",
{groupId},
),
description: errorList.join(", "),
},
);
});
}

View file

@ -163,7 +163,7 @@ export default class IdentityAuthClient {
</div>
),
button: _t("Trust"),
});
});
const [confirmed] = await finished;
if (confirmed) {
// eslint-disable-next-line react-hooks/rules-of-hooks

View file

@ -54,7 +54,7 @@ export default class PasswordReset {
return res;
}, function(err) {
if (err.errcode === 'M_THREEPID_NOT_FOUND') {
err.message = _t('This email address was not found');
err.message = _t('This email address was not found');
} else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`;
}

View file

@ -547,17 +547,23 @@ function textForMjolnirEvent(event) {
// else the entity !== prevEntity - count as a removal & add
if (USER_RULE_TYPES.includes(event.getType())) {
return _t("%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " +
return _t(
"%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s",
{senderName, oldGlob: prevEntity, newGlob: entity, reason});
{senderName, oldGlob: prevEntity, newGlob: entity, reason},
);
} else if (ROOM_RULE_TYPES.includes(event.getType())) {
return _t("%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " +
return _t(
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s",
{senderName, oldGlob: prevEntity, newGlob: entity, reason});
{senderName, oldGlob: prevEntity, newGlob: entity, reason},
);
} else if (SERVER_RULE_TYPES.includes(event.getType())) {
return _t("%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " +
return _t(
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s",
{senderName, oldGlob: prevEntity, newGlob: entity, reason});
{senderName, oldGlob: prevEntity, newGlob: entity, reason},
);
}
// Unknown type. We'll say something but we shouldn't end up here.

View file

@ -310,7 +310,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
<p>{_t(
"Please enter your Security Phrase a second time to confirm.",
"Enter your Security Phrase a second time to confirm it.",
)}</p>
<div className="mx_CreateKeyBackupDialog_primaryContainer">
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
@ -498,9 +498,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
title={this._titleForPhase(this.state.phase)}
hasCancel={[PHASE_PASSPHRASE, PHASE_DONE].includes(this.state.phase)}
>
<div>
{content}
</div>
<div>
{content}
</div>
</BaseDialog>
);
}

View file

@ -647,7 +647,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
}
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
<p>{_t(
"Enter your recovery passphrase a second time to confirm it.",
"Enter your Security Phrase a second time to confirm it.",
)}</p>
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
<Field
@ -655,7 +655,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
onChange={this._onPassPhraseConfirmChange}
value={this.state.passPhraseConfirm}
className="mx_CreateSecretStorageDialog_passPhraseField"
label={_t("Confirm your recovery passphrase")}
label={_t("Confirm your Security Phrase")}
autoFocus={true}
autoComplete="new-password"
/>
@ -856,9 +856,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)}
fixedWidth={false}
>
<div>
{content}
</div>
<div>
{content}
</div>
</BaseDialog>
);
}

View file

@ -170,8 +170,11 @@ export default class ExportE2eKeysDialog extends React.Component {
</div>
</div>
<div className='mx_Dialog_buttons'>
<input className='mx_Dialog_primary' type='submit' value={_t('Export')}
disabled={disableForm}
<input
className='mx_Dialog_primary'
type='submit'
value={_t('Export')}
disabled={disableForm}
/>
<button onClick={this._onCancelClick} disabled={disableForm}>
{ _t("Cancel") }

View file

@ -140,36 +140,36 @@ export default class ImportE2eKeysDialog extends React.Component {
</div>
<div className='mx_E2eKeysDialog_inputTable'>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='importFile'>
{ _t("File to import") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<input
ref={this._file}
id='importFile'
type='file'
autoFocus={true}
onChange={this._onFormChange}
disabled={disableForm} />
</div>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='importFile'>
{ _t("File to import") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<input
ref={this._file}
id='importFile'
type='file'
autoFocus={true}
onChange={this._onFormChange}
disabled={disableForm} />
</div>
</div>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase'>
{ _t("Enter passphrase") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<input
ref={this._passphrase}
id='passphrase'
size='64'
type='password'
onChange={this._onFormChange}
disabled={disableForm} />
</div>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase'>
{ _t("Enter passphrase") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<input
ref={this._passphrase}
id='passphrase'
size='64'
type='password'
onChange={this._onFormChange}
disabled={disableForm} />
</div>
</div>
</div>
</div>

View file

@ -200,10 +200,10 @@ class FilePanel extends React.Component {
previousPhase={RightPanelPhases.RoomSummary}
>
<div className="mx_RoomView_empty">
{ _t("You must <a>register</a> to use this functionality",
{},
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
}
{ _t("You must <a>register</a> to use this functionality",
{},
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
}
</div>
</BaseCard>;
} else if (this.noRoom) {

View file

@ -160,17 +160,17 @@ class GroupFilterPanel extends React.Component {
type="draggable-TagTile"
>
{ (provided, snapshot) => (
<div
className="mx_GroupFilterPanel_tagTileContainer"
ref={provided.innerRef}
>
{ this.renderGlobalIcon() }
{ tags }
<div>
{createButton}
</div>
{ provided.placeholder }
<div
className="mx_GroupFilterPanel_tagTileContainer"
ref={provided.innerRef}
>
{ this.renderGlobalIcon() }
{ tags }
<div>
{createButton}
</div>
{ provided.placeholder }
</div>
) }
</Droppable>
</AutoHideScrollbar>

View file

@ -43,7 +43,7 @@ import {mediaFromMxc} from "../../customisations/Media";
import {replaceableComponent} from "../../utils/replaceableComponent";
const LONG_DESC_PLACEHOLDER = _td(
`<h1>HTML for your community's page</h1>
`<h1>HTML for your community's page</h1>
<p>
Use the long description to introduce new members to the community, or distribute
some important <a href="foo">links</a>
@ -110,14 +110,16 @@ class CategoryRoomList extends React.Component {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
'',
ErrorDialog,
{
title: _t(
"Failed to add the following rooms to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
},
);
});
},
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
@ -146,8 +148,8 @@ class CategoryRoomList extends React.Component {
let catHeader = <div />;
if (this.props.category && this.props.category.profile) {
catHeader = <div className="mx_GroupView_featuredThings_category">
{ this.props.category.profile.name }
</div>;
{ this.props.category.profile.name }
</div>;
}
return <div className="mx_GroupView_featuredThings_container">
{ catHeader }
@ -190,13 +192,14 @@ class FeaturedRoom extends React.Component {
Modal.createTrackedDialog(
'Failed to remove room from group summary',
'', ErrorDialog,
{
title: _t(
"Failed to remove the room from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
});
{
title: _t(
"Failed to remove the room from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
},
);
});
};
@ -283,13 +286,14 @@ class RoleUserList extends React.Component {
Modal.createTrackedDialog(
'Failed to add the following users to the community summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following users to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
{
title: _t(
"Failed to add the following users to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
},
);
});
},
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
@ -299,11 +303,11 @@ class RoleUserList extends React.Component {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
<div className="mx_GroupView_featuredThings_addButton_label">
{ _t('Add a User') }
</div>
</AccessibleButton>) : <div />;
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
<div className="mx_GroupView_featuredThings_addButton_label">
{ _t('Add a User') }
</div>
</AccessibleButton>) : <div />;
const userNodes = this.props.users.map((u) => {
return <FeaturedUser
key={u.user_id}
@ -352,14 +356,16 @@ class FeaturedUser extends React.Component {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to remove user from community summary',
'', ErrorDialog,
{
title: _t(
"Failed to remove a user from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
});
'',
ErrorDialog,
{
title: _t(
"Failed to remove a user from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
},
);
});
};
@ -767,8 +773,8 @@ export default class GroupView extends React.Component {
title: _t("Leave Community"),
description: (
<span>
{ _t("Leave %(groupName)s?", {groupName: this.props.groupId}) }
{ warnings }
{ _t("Leave %(groupName)s?", {groupName: this.props.groupId}) }
{ warnings }
</span>
),
button: _t("Leave"),
@ -1055,10 +1061,11 @@ export default class GroupView extends React.Component {
return null;
}
const membershipButtonClasses = classnames([
'mx_RoomHeader_textButton',
'mx_GroupView_textButton',
],
const membershipButtonClasses = classnames(
[
'mx_RoomHeader_textButton',
'mx_GroupView_textButton',
],
membershipButtonExtraClasses,
);

View file

@ -347,7 +347,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
if (element) {
classes = element.classList;
}
} while (element && !cssClasses.some(c => classes.contains(c)));
} while (element && (!cssClasses.some(c => classes.contains(c)) || element.offsetParent === null));
if (element) {
element.focus();
@ -416,7 +416,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
const roomList = <RoomList
onKeyDown={this.onKeyDown}
resizeNotifier={null}
resizeNotifier={this.props.resizeNotifier}
onFocus={this.onFocus}
onBlur={this.onBlur}
isMinimized={this.props.isMinimized}

View file

@ -427,8 +427,10 @@ export default class MessagePanel extends React.Component {
// we get a new DOM node (restarting the animation) when the ghost
// moves to a different event.
return (
<li key={"_readuptoghost_"+eventId}
className="mx_RoomView_myReadMarker_container">
<li
key={"_readuptoghost_"+eventId}
className="mx_RoomView_myReadMarker_container"
>
{ hr }
</li>
);
@ -1014,13 +1016,13 @@ class CreationGrouper {
ret.push(
<EventListSummary
key="roomcreationsummary"
events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state
summaryMembers={[ev.sender]}
summaryText={summaryText}
key="roomcreationsummary"
events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state
summaryMembers={[ev.sender]}
summaryText={summaryText}
>
{ eventTiles }
{ eventTiles }
</EventListSummary>,
);
@ -1222,11 +1224,11 @@ class MemberGrouper {
ret.push(
<MemberEventListSummary key={key}
events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state
startExpanded={highlightInMels}
events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state
startExpanded={highlightInMels}
>
{ eventTiles }
{ eventTiles }
</MemberEventListSummary>,
);

View file

@ -17,6 +17,8 @@ limitations under the License.
import * as React from "react";
import { createRef } from "react";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { _t } from "../../languageHandler";
import { ActionPayload } from "../../dispatcher/payloads";
@ -26,7 +28,7 @@ import RoomListStore from "../../stores/room-list/RoomListStore";
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
import {replaceableComponent} from "../../utils/replaceableComponent";
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
import SpaceStore, {UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES} from "../../stores/SpaceStore";
interface IProps {
isMinimized: boolean;
@ -40,6 +42,7 @@ interface IProps {
interface IState {
query: string;
focused: boolean;
inSpaces: boolean;
}
@replaceableComponent("structures.RoomSearch")
@ -54,11 +57,13 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
this.state = {
query: "",
focused: false,
inSpaces: false,
};
this.dispatcherRef = defaultDispatcher.register(this.onAction);
// clear filter when changing spaces, in future we may wish to maintain a filter per-space
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput);
SpaceStore.instance.on(UPDATE_TOP_LEVEL_SPACES, this.onSpaces);
}
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
@ -79,8 +84,15 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
public componentWillUnmount() {
defaultDispatcher.unregister(this.dispatcherRef);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput);
SpaceStore.instance.off(UPDATE_TOP_LEVEL_SPACES, this.onSpaces);
}
private onSpaces = (spaces: Room[]) => {
this.setState({
inSpaces: spaces.length > 0,
});
};
private onAction = (payload: ActionPayload) => {
if (payload.action === 'view_room' && payload.clear_search) {
this.clearInput();
@ -152,6 +164,11 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
'mx_RoomSearch_inputExpanded': this.state.query || this.state.focused,
});
let placeholder = _t("Filter");
if (this.state.inSpaces) {
placeholder = _t("Filter all spaces");
}
let icon = (
<div className='mx_RoomSearch_icon' />
);
@ -165,7 +182,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
onBlur={this.onBlur}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
placeholder={_t("Filter")}
placeholder={placeholder}
autoComplete="off"
/>
);

View file

@ -200,20 +200,22 @@ export default class RoomStatusBar extends React.Component {
} else if (resourceLimitError) {
title = messageForResourceLimitError(
resourceLimitError.data.limit_type,
resourceLimitError.data.admin_contact, {
'monthly_active_user': _td(
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
'hs_disabled': _td(
"Your message wasn't sent because this homeserver has been blocked by it's administrator. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
'': _td(
"Your message wasn't sent because this homeserver has exceeded a resource limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
});
resourceLimitError.data.admin_contact,
{
'monthly_active_user': _td(
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
'hs_disabled': _td(
"Your message wasn't sent because this homeserver has been blocked by it's administrator. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
'': _td(
"Your message wasn't sent because this homeserver has exceeded a resource limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
},
);
} else {
title = _t('Some of your messages have not been sent');
}
@ -265,7 +267,7 @@ export default class RoomStatusBar extends React.Component {
<div role="alert">
<div className="mx_RoomStatusBar_connectionLostBar">
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24"
height="24" title="/!\ " alt="/!\ " />
height="24" title="/!\ " alt="/!\ " />
<div>
<div className="mx_RoomStatusBar_connectionLostBar_title">
{_t('Connectivity to the server has been lost.')}

View file

@ -190,6 +190,9 @@ export interface IState {
rejectError?: Error;
hasPinnedWidgets?: boolean;
dragCounter: number;
// whether or not a spaces context switch brought us here,
// if it did we don't want the room to be marked as read as soon as it is loaded.
wasContextSwitch?: boolean;
}
@replaceableComponent("structures.RoomView")
@ -326,6 +329,7 @@ export default class RoomView extends React.Component<IProps, IState> {
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId),
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
wasContextSwitch: RoomViewStore.getWasContextSwitch(),
};
if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
@ -2014,6 +2018,7 @@ export default class RoomView extends React.Component<IProps, IState> {
timelineSet={this.state.room.getUnfilteredTimelineSet()}
showReadReceipts={this.state.showReadReceipts}
manageReadReceipts={!this.state.isPeeking}
sendReadReceiptOnLoad={!this.state.wasContextSwitch}
manageReadMarkers={!this.state.isPeeking}
hidden={hideMessagePanel}
highlightedEventId={highlightedEventId}

View file

@ -884,16 +884,20 @@ export default class ScrollPanel extends React.Component {
// give the <ol> an explicit role=list because Safari+VoiceOver seems to think an ordered-list with
// list-style-type: none; is no longer a list
return (<AutoHideScrollbar wrappedRef={this._collectScroll}
return (
<AutoHideScrollbar
wrappedRef={this._collectScroll}
onScroll={this.onScroll}
className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}>
{ this.props.fixedChildren }
<div className="mx_RoomView_messageListWrapper">
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
{ this.props.children }
</ol>
</div>
</AutoHideScrollbar>
);
className={`mx_ScrollPanel ${this.props.className}`}
style={this.props.style}
>
{ this.props.fixedChildren }
<div className="mx_RoomView_messageListWrapper">
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
{ this.props.children }
</ol>
</div>
</AutoHideScrollbar>
);
}
}

View file

@ -39,6 +39,7 @@ import {mediaFromMxc} from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip";
import {useStateToggle} from "../../hooks/useStateToggle";
import {getOrder} from "../../stores/SpaceStore";
interface IHierarchyProps {
space: Room;
@ -254,7 +255,11 @@ export const HierarchyLevel = ({
const space = cli.getRoom(spaceId);
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
const sortedChildren = sortBy([...(relations.get(spaceId)?.values() || [])], ev => ev.content.order || null);
const children = Array.from(relations.get(spaceId)?.values() || []);
const sortedChildren = sortBy(children, ev => {
// XXX: Space Summary API doesn't give the child origin_server_ts but once it does we should use it for sorting
return getOrder(ev.content.order, null, ev.state_key);
});
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
const roomId = ev.state_key;
if (!rooms.has(roomId)) return result;

View file

@ -52,7 +52,7 @@ import {useStateToggle} from "../../hooks/useStateToggle";
import SpaceStore from "../../stores/SpaceStore";
import FacePile from "../views/elements/FacePile";
import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog";
import {allSettled} from "../../utils/promise";
import {sleep} from "../../utils/promise";
import {calculateRoomVia} from "../../utils/permalinks/Permalinks";
import {BetaPill} from "../views/beta/BetaCard";
import {USER_LABS_TAB} from "../views/dialogs/UserSettingsDialog";
@ -434,15 +434,24 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
let buttonLabel = _t("Skip for now");
if (selectedToAdd.size > 0) {
onClick = async () => {
// TODO rate limiting
setBusy(true);
try {
await allSettled(Array.from(selectedToAdd).map((room) =>
SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room))));
onFinished(true);
} catch (e) {
console.error("Failed to add rooms to space", e);
setError(_t("Failed to add rooms to space"));
for (const room of selectedToAdd) {
const via = calculateRoomVia(room);
try {
await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => {
if (e.errcode === "M_LIMIT_EXCEEDED") {
await sleep(e.data.retry_after_ms);
return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry
}
throw e;
});
} catch (e) {
console.error("Failed to add rooms to space", e);
setError(_t("Failed to add rooms to space"));
break;
}
}
setBusy(false);
};

View file

@ -68,6 +68,7 @@ class TimelinePanel extends React.Component {
showReadReceipts: PropTypes.bool,
// Enable managing RRs and RMs. These require the timelineSet to have a room.
manageReadReceipts: PropTypes.bool,
sendReadReceiptOnLoad: PropTypes.bool,
manageReadMarkers: PropTypes.bool,
// true to give the component a 'display: none' style.
@ -126,6 +127,7 @@ class TimelinePanel extends React.Component {
// event tile heights. (See _unpaginateEvents)
timelineCap: Number.MAX_VALUE,
className: 'mx_RoomView_messagePanel',
sendReadReceiptOnLoad: true,
};
constructor(props) {
@ -785,8 +787,10 @@ class TimelinePanel extends React.Component {
return;
}
const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
this._setReadMarker(lastDisplayedEvent.getId(),
lastDisplayedEvent.getTs());
this._setReadMarker(
lastDisplayedEvent.getId(),
lastDisplayedEvent.getTs(),
);
// the read-marker should become invisible, so that if the user scrolls
// down, they don't see it.
@ -872,7 +876,7 @@ class TimelinePanel extends React.Component {
// The messagepanel knows where the RM is, so we must have loaded
// the relevant event.
this._messagePanel.current.scrollToEvent(this.state.readMarkerEventId,
0, 1/3);
0, 1/3);
return;
}
@ -1044,12 +1048,14 @@ class TimelinePanel extends React.Component {
}
if (eventId) {
this._messagePanel.current.scrollToEvent(eventId, pixelOffset,
offsetBase);
offsetBase);
} else {
this._messagePanel.current.scrollToBottom();
}
this.sendReadReceipt();
if (this.props.sendReadReceiptOnLoad) {
this.sendReadReceipt();
}
});
};
@ -1418,8 +1424,8 @@ class TimelinePanel extends React.Component {
['PREPARED', 'CATCHUP'].includes(this.state.clientSyncState)
);
const events = this.state.firstVisibleEventIndex
? this.state.events.slice(this.state.firstVisibleEventIndex)
: this.state.events;
? this.state.events.slice(this.state.firstVisibleEventIndex)
: this.state.events;
return (
<MessagePanel
ref={this._messagePanel}

View file

@ -169,7 +169,7 @@ export class PasswordAuthEntry extends React.Component {
{ submitButtonOrSpinner }
</div>
</form>
{ errorSection }
{ errorSection }
</div>
);
}
@ -375,7 +375,7 @@ export class TermsAuthEntry extends React.Component {
if (this.props.showContinue !== false) {
// XXX: button classes
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
}
return (

View file

@ -350,7 +350,7 @@ export default class MessageContextMenu extends React.Component {
>
{ _t('Source URL') }
</MenuItem>
);
);
}
if (this.props.collapseReplyThread) {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {useContext, useState} from "react";
import React, {useContext, useMemo, useState} from "react";
import classNames from "classnames";
import {Room} from "matrix-js-sdk/src/models/room";
import {MatrixClient} from "matrix-js-sdk/src/client";
@ -29,11 +29,13 @@ import RoomAvatar from "../avatars/RoomAvatar";
import {getDisplayAliasForRoom} from "../../../Rooms";
import AccessibleButton from "../elements/AccessibleButton";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {allSettled} from "../../../utils/promise";
import {sleep} from "../../../utils/promise";
import DMRoomMap from "../../../utils/DMRoomMap";
import {calculateRoomVia} from "../../../utils/permalinks/Permalinks";
import StyledCheckbox from "../elements/StyledCheckbox";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import ProgressBar from "../elements/ProgressBar";
interface IProps extends IDialogProps {
matrixClient: MatrixClient;
@ -45,7 +47,11 @@ const Entry = ({ room, checked, onChange }) => {
return <label className="mx_AddExistingToSpace_entry">
<RoomAvatar room={room} height={32} width={32} />
<span className="mx_AddExistingToSpace_entry_name">{ room.name }</span>
<StyledCheckbox onChange={(e) => onChange(e.target.checked)} checked={checked} />
<StyledCheckbox
onChange={onChange ? (e) => onChange(e.target.checked) : null}
checked={checked}
disabled={!onChange}
/>
</label>;
};
@ -57,6 +63,8 @@ interface IAddExistingToSpaceProps {
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space, selected, onChange }) => {
const cli = useContext(MatrixClientContext);
const visibleRooms = useMemo(() => sortRooms(cli.getVisibleRooms()), [cli]);
const [query, setQuery] = useState("");
const lcQuery = query.toLowerCase();
@ -65,7 +73,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
const existingRoomsSet = new Set(SpaceStore.instance.getChildRooms(space.roomId));
const joinRule = space.getJoinRule();
const [spaces, rooms, dms] = cli.getVisibleRooms().reduce((arr, room) => {
const [spaces, rooms, dms] = visibleRooms.reduce((arr, room) => {
if (room.getMyMembership() !== "join") return arr;
if (!room.name.toLowerCase().includes(lcQuery)) return arr;
@ -101,9 +109,9 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
key={room.roomId}
room={room}
checked={selected.has(room)}
onChange={(checked) => {
onChange={onChange ? (checked) => {
onChange(checked, room);
}}
} : null}
/>;
}) }
</div>
@ -117,9 +125,9 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
key={space.roomId}
room={space}
checked={selected.has(space)}
onChange={(checked) => {
onChange={onChange ? (checked) => {
onChange(checked, space);
}}
} : null}
/>;
}) }
</div>
@ -133,9 +141,9 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
key={room.roomId}
room={room}
checked={selected.has(room)}
onChange={(checked) => {
onChange={onChange ? (checked) => {
onChange(checked, room);
}}
} : null}
/>;
}) }
</div>
@ -153,8 +161,8 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const [progress, setProgress] = useState<number>(null);
const [error, setError] = useState<Error>(null);
let spaceOptionSection;
if (existingSubspaces.length > 0) {
@ -194,6 +202,82 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
</div>
</React.Fragment>;
const addRooms = async () => {
setError(null);
setProgress(0);
let error;
for (const room of selectedToAdd) {
const via = calculateRoomVia(room);
try {
await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => {
if (e.errcode === "M_LIMIT_EXCEEDED") {
await sleep(e.data.retry_after_ms);
return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry
}
throw e;
});
setProgress(i => i + 1);
} catch (e) {
console.error("Failed to add rooms to space", e);
setError(error = e);
break;
}
}
if (!error) {
onFinished(true);
}
};
const busy = progress !== null;
let footer;
if (error) {
footer = <>
<img
src={require("../../../../res/img/element-icons/warning-badge.svg")}
height="24"
width="24"
alt=""
/>
<span className="mx_AddExistingToSpaceDialog_error">
<div className="mx_AddExistingToSpaceDialog_errorHeading">{ _t("Not all selected were added") }</div>
<div className="mx_AddExistingToSpaceDialog_errorCaption">{ _t("Try again") }</div>
</span>
<AccessibleButton className="mx_AddExistingToSpaceDialog_retryButton" onClick={addRooms}>
{ _t("Retry") }
</AccessibleButton>
</>;
} else if (busy) {
footer = <span>
<ProgressBar value={progress} max={selectedToAdd.size} />
<div className="mx_AddExistingToSpaceDialog_progressText">
{ _t("Adding rooms... (%(progress)s out of %(count)s)", {
count: selectedToAdd.size,
progress,
}) }
</div>
</span>;
} else {
footer = <>
<span>
<div>{ _t("Want to add a new room instead?") }</div>
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
{ _t("Create a new room") }
</AccessibleButton>
</span>
<AccessibleButton kind="primary" disabled={selectedToAdd.size < 1} onClick={addRooms}>
{ _t("Add") }
</AccessibleButton>
</>;
}
return <BaseDialog
title={title}
className="mx_AddExistingToSpaceDialog"
@ -201,50 +285,23 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
onFinished={onFinished}
fixedWidth={false}
>
{ error && <div className="mx_AddExistingToSpaceDialog_errorText">{ error }</div> }
<MatrixClientContext.Provider value={cli}>
<AddExistingToSpace
space={space}
selected={selectedToAdd}
onChange={(checked, room) => {
onChange={!busy && !error ? (checked, room) => {
if (checked) {
selectedToAdd.add(room);
} else {
selectedToAdd.delete(room);
}
setSelectedToAdd(new Set(selectedToAdd));
}}
} : null}
/>
</MatrixClientContext.Provider>
<div className="mx_AddExistingToSpaceDialog_footer">
<span>
<div>{ _t("Don't want to add an existing room?") }</div>
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
{ _t("Create a new room") }
</AccessibleButton>
</span>
<AccessibleButton
kind="primary"
disabled={busy || selectedToAdd.size < 1}
onClick={async () => {
// TODO rate limiting
setBusy(true);
try {
await allSettled(Array.from(selectedToAdd).map((room) =>
SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room))));
onFinished(true);
} catch (e) {
console.error("Failed to add rooms to space", e);
setError(_t("Failed to add rooms to space"));
}
setBusy(false);
}}
>
{ busy ? _t("Adding...") : _t("Add") }
</AccessibleButton>
{ footer }
</div>
</BaseDialog>;
};

View file

@ -184,7 +184,7 @@ export default class BugReportDialog extends React.Component {
return (
<BaseDialog className="mx_BugReportDialog" onFinished={this._onCancel}
title={_t('Submit debug logs')}
title={_t('Submit debug logs')}
contentId='mx_Dialog_content'
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>

View file

@ -95,7 +95,7 @@ export default class ChangelogDialog extends React.Component {
description={content}
button={_t("Update")}
onFinished={this.props.onFinished}
/>
/>
);
}
}

View file

@ -39,9 +39,12 @@ export default class ConfirmWipeDeviceDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<BaseDialog className='mx_ConfirmWipeDeviceDialog' hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Clear all data in this session?")}>
<BaseDialog
className='mx_ConfirmWipeDeviceDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Clear all data in this session?")}
>
<div className='mx_ConfirmWipeDeviceDialog_content'>
<p>
{_t(

View file

@ -70,8 +70,16 @@ class GenericEditor extends React.PureComponent {
}
textInput(id, label) {
return <Field id={id} label={label} size="42" autoFocus={true} type="text" autoComplete="on"
value={this.state[id]} onChange={this._onChange} />;
return <Field
id={id}
label={label}
size="42"
autoFocus={true}
type="text"
autoComplete="on"
value={this.state[id]}
onChange={this._onChange}
/>;
}
}
@ -155,7 +163,7 @@ export class SendCustomEvent extends GenericEditor {
<br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
@ -239,7 +247,7 @@ class SendAccountData extends GenericEditor {
<br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
@ -315,15 +323,15 @@ class FilteredList extends React.PureComponent {
const TruncatedList = sdk.getComponent("elements.TruncatedList");
return <div>
<Field label={_t('Filter results')} autoFocus={true} size={64}
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
// force re-render so that autoFocus is applied when this component is re-used
key={this.props.children[0] ? this.props.children[0].key : ''} />
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
// force re-render so that autoFocus is applied when this component is re-used
key={this.props.children[0] ? this.props.children[0].key : ''} />
<TruncatedList getChildren={this.getChildren}
getChildCount={this.getChildCount}
truncateAt={this.state.truncateAt}
createOverflowElement={this.createOverflowElement} />
getChildCount={this.getChildCount}
truncateAt={this.state.truncateAt}
createOverflowElement={this.createOverflowElement} />
</div>;
}
}
@ -647,7 +655,7 @@ function VerificationRequest({txnId, request}) {
/* Note that request.timeout is a getter, so its value changes */
const id = setInterval(() => {
setRequestTimeout(request.timeout);
setRequestTimeout(request.timeout);
}, 500);
return () => { clearInterval(id); };
@ -941,35 +949,35 @@ class SettingsExplorer extends React.Component {
/>
<table>
<thead>
<tr>
<th>{_t("Setting ID")}</th>
<th>{_t("Value")}</th>
<th>{_t("Value in this room")}</th>
</tr>
<tr>
<th>{_t("Setting ID")}</th>
<th>{_t("Value")}</th>
<th>{_t("Value in this room")}</th>
</tr>
</thead>
<tbody>
{allSettings.map(i => (
<tr key={i}>
<td>
<a href="" onClick={(e) => this.onViewClick(e, i)}>
<code>{i}</code>
</a>
<a href="" onClick={(e) => this.onEditClick(e, i)}
className='mx_DevTools_SettingsExplorer_edit'
>
{allSettings.map(i => (
<tr key={i}>
<td>
<a href="" onClick={(e) => this.onViewClick(e, i)}>
<code>{i}</code>
</a>
<a href="" onClick={(e) => this.onEditClick(e, i)}
className='mx_DevTools_SettingsExplorer_edit'
>
</a>
</td>
<td>
<code>{this.renderSettingValue(SettingsStore.getValue(i))}</code>
</td>
<td>
<code>
{this.renderSettingValue(SettingsStore.getValue(i, room.roomId))}
</code>
</td>
</tr>
))}
</a>
</td>
<td>
<code>{this.renderSettingValue(SettingsStore.getValue(i))}</code>
</td>
<td>
<code>
{this.renderSettingValue(SettingsStore.getValue(i, room.roomId))}
</code>
</td>
</tr>
))}
</tbody>
</table>
</div>
@ -998,11 +1006,11 @@ class SettingsExplorer extends React.Component {
<div>
<table>
<thead>
<tr>
<th>{_t("Level")}</th>
<th>{_t("Settable at global")}</th>
<th>{_t("Settable at room")}</th>
</tr>
<tr>
<th>{_t("Level")}</th>
<th>{_t("Settable at global")}</th>
<th>{_t("Settable at room")}</th>
</tr>
</thead>
<tbody>
{LEVEL_ORDER.map(lvl => (

View file

@ -42,9 +42,12 @@ export default class IntegrationsDisabledDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<BaseDialog className='mx_IntegrationsDisabledDialog' hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Integrations are disabled")}>
<BaseDialog
className='mx_IntegrationsDisabledDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Integrations are disabled")}
>
<div className='mx_IntegrationsDisabledDialog_content'>
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
</div>

View file

@ -37,9 +37,12 @@ export default class IntegrationsImpossibleDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<BaseDialog className='mx_IntegrationsImpossibleDialog' hasCancel={false}
onFinished={this.props.onFinished}
title={_t("Integrations not allowed")}>
<BaseDialog
className='mx_IntegrationsImpossibleDialog'
hasCancel={false}
onFinished={this.props.onFinished}
title={_t("Integrations not allowed")}
>
<div className='mx_IntegrationsImpossibleDialog_content'>
<p>
{_t(

View file

@ -24,7 +24,7 @@ export default function KeySignatureUploadFailedDialog({
source,
continuation,
onFinished,
}) {
}) {
const RETRIES = 2;
const BaseDialog = sdk.getComponent('dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -84,10 +84,10 @@ export default function KeySignatureUploadFailedDialog({
} else {
body = (<div>
{success ?
<span>{_t("Upload completed")}</span> :
cancelled ?
<span>{_t("Cancelled signature upload")}</span> :
<span>{_t("Unable to upload")}</span>}
<span>{_t("Upload completed")}</span> :
cancelled ?
<span>{_t("Cancelled signature upload")}</span> :
<span>{_t("Unable to upload")}</span>}
<DialogButtons
primaryButton={_t("OK")}
hasCancel={false}

View file

@ -164,8 +164,12 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className='mx_MessageEditHistoryDialog' hasCancel={true}
onFinished={this.props.onFinished} title={_t("Message edits")}>
<BaseDialog
className='mx_MessageEditHistoryDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Message edits")}
>
{content}
</BaseDialog>
);

View file

@ -116,8 +116,12 @@ export default class RoomSettingsDialog extends React.Component {
const roomName = MatrixClientPeg.get().getRoom(this.props.roomId).name;
return (
<BaseDialog className='mx_RoomSettingsDialog' hasCancel={true}
onFinished={this.props.onFinished} title={_t("Room Settings - %(roomName)s", {roomName})}>
<BaseDialog
className='mx_RoomSettingsDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Room Settings - %(roomName)s", {roomName})}
>
<div className='mx_SettingsDialog_content'>
<TabbedView tabs={this._getTabs()} />
</div>

View file

@ -98,7 +98,7 @@ export default class SessionRestoreErrorDialog extends React.Component {
"may be incompatible with this version. Close this window and return " +
"to the more recent version.",
{ brand },
) }</p>
) }</p>
<p>{ _t(
"Clearing your browser's storage may fix the problem, but will sign you " +

View file

@ -45,10 +45,12 @@ export default class StorageEvictedDialog extends React.Component {
let logRequest;
if (SdkConfig.get().bug_report_endpoint_url) {
logRequest = _t(
"To help us prevent this in future, please <a>send us logs</a>.", {},
{
a: text => <a href="#" onClick={this._sendBugReport}>{text}</a>,
});
"To help us prevent this in future, please <a>send us logs</a>.",
{},
{
a: text => <a href="#" onClick={this._sendBugReport}>{text}</a>,
},
);
}
return (

View file

@ -158,8 +158,12 @@ export default class UserSettingsDialog extends React.Component {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className='mx_UserSettingsDialog' hasCancel={true}
onFinished={this.props.onFinished} title={_t("Settings")}>
<BaseDialog
className='mx_UserSettingsDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Settings")}
>
<div className='mx_SettingsDialog_content'>
<TabbedView tabs={this._getTabs()} initialTabId={this.props.initialTabId} />
</div>

View file

@ -52,11 +52,13 @@ export default class VerificationRequestDialog extends React.Component {
const title = request && request.isSelfVerification ?
_t("Verify other login") : _t("Verification Request");
return <BaseDialog className="mx_InfoDialog" onFinished={this.props.onFinished}
contentId="mx_Dialog_content"
title={title}
hasCancel={true}
>
return <BaseDialog
className="mx_InfoDialog"
onFinished={this.props.onFinished}
contentId="mx_Dialog_content"
title={title}
hasCancel={true}
>
<EncryptionPanel
layout="dialog"
verificationRequest={this.props.verificationRequest}

View file

@ -70,9 +70,12 @@ export default class WidgetOpenIDPermissionsDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<BaseDialog className='mx_WidgetOpenIDPermissionsDialog' hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Allow this widget to verify your identity")}>
<BaseDialog
className='mx_WidgetOpenIDPermissionsDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Allow this widget to verify your identity")}
>
<div className='mx_WidgetOpenIDPermissionsDialog_content'>
<p>
{_t("The widget will verify your user ID, but won't be able to perform actions for you:")}

View file

@ -40,10 +40,11 @@ export default class ConfirmDestroyCrossSigningDialog extends React.Component {
return (
<BaseDialog
className='mx_ConfirmDestroyCrossSigningDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Destroy cross-signing keys?")}>
className='mx_ConfirmDestroyCrossSigningDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Destroy cross-signing keys?")}
>
<div className='mx_ConfirmDestroyCrossSigningDialog_content'>
<p>
{_t(

View file

@ -373,21 +373,24 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
{_t(
"If you've forgotten your Security Phrase you can "+
"<button1>use your Security Key</button1> or " +
"<button2>set up new recovery options</button2>"
, {}, {
button1: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onUseRecoveryKeyClick}
>
{s}
</AccessibleButton>,
button2: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
</AccessibleButton>,
})}
"<button2>set up new recovery options</button2>",
{},
{
button1: s => <AccessibleButton
className="mx_linkButton"
element="span"
onClick={this._onUseRecoveryKeyClick}
>
{s}
</AccessibleButton>,
button2: s => <AccessibleButton
className="mx_linkButton"
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
</AccessibleButton>,
})}
</div>;
} else {
title = _t("Enter Security Key");
@ -435,15 +438,17 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
</div>
{_t(
"If you've forgotten your Security Key you can "+
"<button>set up new recovery options</button>"
, {}, {
button: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
</AccessibleButton>,
})}
"<button>set up new recovery options</button>",
{},
{
button: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
</AccessibleButton>,
},
)}
</div>;
}
@ -452,9 +457,9 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
onFinished={this.props.onFinished}
title={title}
>
<div className='mx_RestoreKeyBackupDialog_content'>
{content}
</div>
<div className='mx_RestoreKeyBackupDialog_content'>
{content}
</div>
</BaseDialog>
);
}

View file

@ -71,8 +71,8 @@ export default class ActionButton extends React.Component {
}
const icon = this.props.iconPath ?
(<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
undefined;
(<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
undefined;
const classNames = ["mx_RoleButton"];
if (this.props.className) {

View file

@ -109,7 +109,7 @@ export default class AppTile extends React.Component {
const childContentProtocol = u.protocol;
if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
console.warn("Refusing to load mixed-content app:",
parentContentProtocol, childContentProtocol, window.location, this.props.app.url);
parentContentProtocol, childContentProtocol, window.location, this.props.app.url);
return true;
}
return false;

View file

@ -65,12 +65,18 @@ export class EditableItem extends React.Component {
<span className="mx_EditableItem_promptText">
{_t("Are you sure?")}
</span>
<AccessibleButton onClick={this._onActuallyRemove} kind="primary_sm"
className="mx_EditableItem_confirmBtn">
<AccessibleButton
onClick={this._onActuallyRemove}
kind="primary_sm"
className="mx_EditableItem_confirmBtn"
>
{_t("Yes")}
</AccessibleButton>
<AccessibleButton onClick={this._onDontRemove} kind="danger_sm"
className="mx_EditableItem_confirmBtn">
<AccessibleButton
onClick={this._onDontRemove}
kind="danger_sm"
className="mx_EditableItem_confirmBtn"
>
{_t("No")}
</AccessibleButton>
</div>
@ -121,11 +127,15 @@ export default class EditableItemList extends React.Component {
_renderNewItemField() {
return (
<form onSubmit={this._onItemAdded} autoComplete="off"
noValidate={true} className="mx_EditableItemList_newItem">
<form
onSubmit={this._onItemAdded}
autoComplete="off"
noValidate={true}
className="mx_EditableItemList_newItem"
>
<Field label={this.props.placeholder} type="text"
autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged}
list={this.props.suggestionsListId} />
autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged}
list={this.props.suggestionsListId} />
<AccessibleButton onClick={this._onItemAdded} kind="primary" type="submit" disabled={!this.props.newItem}>
{_t("Add")}
</AccessibleButton>

View file

@ -221,13 +221,15 @@ export default class EditableText extends React.Component {
</div>;
} else {
// show the content editable div, but manually manage its contents as react and contentEditable don't play nice together
editableEl = <div ref={this._editable_div}
contentEditable={true}
className={className}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur} />;
editableEl = <div
ref={this._editable_div}
contentEditable={true}
className={className}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>;
}
return editableEl;

View file

@ -114,6 +114,8 @@ export default class ImageView extends React.Component<IProps, IState> {
componentWillUnmount() {
this.focusLock.current.removeEventListener('wheel', this.onWheel);
window.removeEventListener("resize", this.calculateZoom);
this.image.current.removeEventListener("load", this.calculateZoom);
}
private calculateZoom = () => {

View file

@ -46,8 +46,12 @@ export default class LabelledToggleSwitch extends React.Component {
// This is a minimal version of a SettingsFlag
let firstPart = <span className="mx_SettingsFlag_label">{this.props.label}</span>;
let secondPart = <ToggleSwitch checked={this.props.value} disabled={this.props.disabled}
onChange={this.props.onChange} aria-label={this.props.label} />;
let secondPart = <ToggleSwitch
checked={this.props.value}
disabled={this.props.disabled}
onChange={this.props.onChange}
aria-label={this.props.label}
/>;
if (this.props.toggleInFront) {
const temp = firstPart;

View file

@ -60,10 +60,10 @@ export default class LanguageDropdown extends React.Component {
// doesn't know this, therefore we do this.
const language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
if (language) {
this.props.onOptionChange(language);
this.props.onOptionChange(language);
} else {
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
this.props.onOptionChange(language);
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
this.props.onOptionChange(language);
}
}
}

View file

@ -225,19 +225,19 @@ class Pill extends React.Component {
}
break;
case Pill.TYPE_USER_MENTION: {
// If this user is not a member of this room, default to the empty member
const member = this.state.member;
if (member) {
userId = member.userId;
member.rawDisplayName = member.rawDisplayName || '';
linkText = member.rawDisplayName;
if (this.props.shouldShowPillAvatar) {
avatar = <MemberAvatar member={member} width={16} height={16} aria-hidden="true" />;
}
pillClass = 'mx_UserPill';
href = null;
onClick = this.onUserPillClicked;
// If this user is not a member of this room, default to the empty member
const member = this.state.member;
if (member) {
userId = member.userId;
member.rawDisplayName = member.rawDisplayName || '';
linkText = member.rawDisplayName;
if (this.props.shouldShowPillAvatar) {
avatar = <MemberAvatar member={member} width={16} height={16} aria-hidden="true" />;
}
pillClass = 'mx_UserPill';
href = null;
onClick = this.onUserPillClicked;
}
}
break;
case Pill.TYPE_ROOM_MENTION: {

View file

@ -135,9 +135,13 @@ export default class PowerSelector extends React.Component {
if (this.state.custom) {
picker = (
<Field type="number"
label={label} max={this.props.maxValue}
onBlur={this.onCustomBlur} onKeyDown={this.onCustomKeyDown} onChange={this.onCustomChange}
value={String(this.state.customValue)} disabled={this.props.disabled} />
label={label} max={this.props.maxValue}
onBlur={this.onCustomBlur}
onKeyDown={this.onCustomKeyDown}
onChange={this.onCustomChange}
value={String(this.state.customValue)}
disabled={this.props.disabled}
/>
);
} else {
// Each level must have a definition in this.state.levelRoleMap
@ -154,8 +158,9 @@ export default class PowerSelector extends React.Component {
picker = (
<Field element="select"
label={label} onChange={this.onSelectChange}
value={String(this.state.selectValue)} disabled={this.props.disabled}>
label={label} onChange={this.onSelectChange}
value={String(this.state.selectValue)} disabled={this.props.disabled}
>
{options}
</Field>
);

View file

@ -46,17 +46,18 @@ export default class RoomAliasField extends React.PureComponent {
const domain = (<span title={aliasPostfix}>{aliasPostfix}</span>);
const maxlength = 255 - this.props.domain.length - 2; // 2 for # and :
return (
<Field
label={_t("Room address")}
className="mx_RoomAliasField"
prefixComponent={poundSign}
postfixComponent={domain}
ref={ref => this._fieldRef = ref}
onValidate={this._onValidate}
placeholder={_t("e.g. my-room")}
onChange={this._onChange}
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
maxLength={maxlength} />
<Field
label={_t("Room address")}
className="mx_RoomAliasField"
prefixComponent={poundSign}
postfixComponent={domain}
ref={ref => this._fieldRef = ref}
onValidate={this._onValidate}
placeholder={_t("e.g. my-room")}
onChange={this._onChange}
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
maxLength={maxlength}
/>
);
}

View file

@ -59,13 +59,13 @@ class TintableSvg extends React.Component {
render() {
return (
<object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")}
type="image/svg+xml"
data={this.props.src}
width={this.props.width}
height={this.props.height}
onLoad={this.onLoad}
tabIndex="-1"
/>
type="image/svg+xml"
data={this.props.src}
width={this.props.width}
height={this.props.height}
onLoad={this.onLoad}
tabIndex="-1"
/>
);
}
}

View file

@ -178,9 +178,15 @@ export default class GroupMemberList extends React.Component {
}
const inputBox = (
<input className="mx_GroupMemberList_query mx_textinput" id="mx_GroupMemberList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={_t('Filter community members')} autoComplete="off" />
<input
className="mx_GroupMemberList_query mx_textinput"
id="mx_GroupMemberList_query"
type="text"
onChange={this.onSearchQueryChanged}
value={this.state.searchQuery}
placeholder={_t('Filter community members')}
autoComplete="off"
/>
);
const joined = this.state.members ? <div className="mx_MemberList_joined">

View file

@ -67,11 +67,11 @@ export default class GroupPublicityToggle extends React.Component {
const GroupTile = sdk.getComponent('groups.GroupTile');
return <div className="mx_GroupPublicity_toggle">
<GroupTile groupId={this.props.groupId} showDescription={false}
avatarHeight={40} draggable={false}
avatarHeight={40} draggable={false}
/>
<ToggleSwitch checked={this.state.isGroupPublicised}
disabled={!this.state.ready || this.state.busy}
onChange={this._onPublicityToggle} />
disabled={!this.state.ready || this.state.busy}
onChange={this._onPublicityToggle} />
</div>;
}
}

View file

@ -141,9 +141,14 @@ export default class GroupRoomList extends React.Component {
);
}
const inputBox = (
<input className="mx_GroupRoomList_query mx_textinput" id="mx_GroupRoomList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={_t('Filter community rooms')} autoComplete="off" />
<input
className="mx_GroupRoomList_query mx_textinput" id="mx_GroupRoomList_query"
type="text"
onChange={this.onSearchQueryChanged}
value={this.state.searchQuery}
placeholder={_t('Filter community rooms')}
autoComplete="off"
/>
);
const TruncatedList = sdk.getComponent("elements.TruncatedList");
@ -152,7 +157,7 @@ export default class GroupRoomList extends React.Component {
{ inviteButton }
<AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}>
createOverflowElement={this._createOverflowTile}>
{ this.makeGroupRoomTiles(this.state.searchQuery) }
</TruncatedList>
</AutoHideScrollbar>

View file

@ -125,9 +125,9 @@ export default class MImageBody extends React.Component {
_isGif() {
const content = this.props.mxEvent.getContent();
return (
content &&
content.info &&
content.info.mimetype === "image/gif"
content &&
content.info &&
content.info.mimetype === "image/gif"
);
}
@ -346,9 +346,9 @@ export default class MImageBody extends React.Component {
} else {
imageElement = (
<img style={{display: 'none'}} src={thumbUrl} ref={this._image}
alt={content.body}
onError={this.onImageError}
onLoad={this.onImageLoad}
alt={content.body}
onError={this.onImageError}
onLoad={this.onImageLoad}
/>
);
}
@ -384,12 +384,12 @@ export default class MImageBody extends React.Component {
// mx_MImageBody_thumbnail resizes img to exactly container size
img = (
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this._image}
style={{ maxWidth: maxWidth + "px" }}
alt={content.body}
onError={this.onImageError}
onLoad={this.onImageLoad}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />
style={{ maxWidth: maxWidth + "px" }}
alt={content.body}
onError={this.onImageError}
onLoad={this.onImageLoad}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />
);
}
@ -467,9 +467,9 @@ export default class MImageBody extends React.Component {
const contentUrl = this._getContentUrl();
let thumbUrl;
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
thumbUrl = contentUrl;
thumbUrl = contentUrl;
} else {
thumbUrl = this._getThumbUrl();
thumbUrl = this._getThumbUrl();
}
const thumbnail = this._messageContent(contentUrl, thumbUrl, content);

View file

@ -82,9 +82,7 @@ export default class MKeyVerificationConclusion extends React.Component {
}
// User isn't actually verified
if (!MatrixClientPeg.get()
.checkUserTrust(request.otherUserId)
.isCrossSigningVerified()) {
if (!MatrixClientPeg.get().checkUserTrust(request.otherUserId).isCrossSigningVerified()) {
return false;
}

View file

@ -0,0 +1,106 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 React from "react";
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {Playback} from "../../../voice/Playback";
import MFileBody from "./MFileBody";
import InlineSpinner from '../elements/InlineSpinner';
import {_t} from "../../../languageHandler";
import {mediaFromContent} from "../../../customisations/Media";
import {decryptFile} from "../../../utils/DecryptFile";
import RecordingPlayback from "../voice_messages/RecordingPlayback";
interface IProps {
mxEvent: MatrixEvent;
}
interface IState {
error?: Error;
playback?: Playback;
decryptedBlob?: Blob;
}
@replaceableComponent("views.messages.MVoiceMessageBody")
export default class MVoiceMessageBody extends React.PureComponent<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {};
}
public async componentDidMount() {
let buffer: ArrayBuffer;
const content = this.props.mxEvent.getContent();
const media = mediaFromContent(content);
if (media.isEncrypted) {
try {
const blob = await decryptFile(content.file);
buffer = await blob.arrayBuffer();
this.setState({decryptedBlob: blob});
} catch (e) {
this.setState({error: e});
console.warn("Unable to decrypt voice message", e);
return; // stop processing the audio file
}
} else {
try {
buffer = await media.downloadSource().then(r => r.blob()).then(r => r.arrayBuffer());
} catch (e) {
this.setState({error: e});
console.warn("Unable to download voice message", e);
return; // stop processing the audio file
}
}
const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map(p => p / 1024);
// We should have a buffer to work with now: let's set it up
const playback = new Playback(buffer, waveform);
this.setState({playback});
// Note: the RecordingPlayback component will handle preparing the Playback class for us.
}
public render() {
if (this.state.error) {
// TODO: @@TR: Verify error state
return (
<span className="mx_MVoiceMessageBody">
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error processing voice message") }
</span>
);
}
if (!this.state.playback) {
// TODO: @@TR: Verify loading/decrypting state
return (
<span className="mx_MVoiceMessageBody">
<InlineSpinner />
</span>
);
}
// At this point we should have a playable state
return (
<span className="mx_MVoiceMessageBody">
<RecordingPlayback playback={this.state.playback} />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />
</span>
)
}
}

View file

@ -0,0 +1,39 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 React from "react";
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import MAudioBody from "./MAudioBody";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import SettingsStore from "../../../settings/SettingsStore";
import MVoiceMessageBody from "./MVoiceMessageBody";
interface IProps {
mxEvent: MatrixEvent;
}
@replaceableComponent("views.messages.MVoiceOrAudioBody")
export default class MVoiceOrAudioBody extends React.PureComponent<IProps> {
public render() {
const isVoiceMessage = !!this.props.mxEvent.getContent()['org.matrix.msc2516.voice'];
const voiceMessagesEnabled = SettingsStore.getValue("feature_voice_messages");
if (isVoiceMessage && voiceMessagesEnabled) {
return <MVoiceMessageBody {...this.props} />;
} else {
return <MAudioBody {...this.props} />;
}
}
}

View file

@ -72,12 +72,8 @@ export default class MessageEvent extends React.Component {
'm.emote': sdk.getComponent('messages.TextualBody'),
'm.image': sdk.getComponent('messages.MImageBody'),
'm.file': sdk.getComponent('messages.MFileBody'),
'm.audio': sdk.getComponent('messages.MAudioBody'),
'm.audio': sdk.getComponent('messages.MVoiceOrAudioBody'),
'm.video': sdk.getComponent('messages.MVideoBody'),
// TODO: @@ TravisR: Use labs flag determination.
// MSC: https://github.com/matrix-org/matrix-doc/pull/2516
'org.matrix.msc2516.voice': sdk.getComponent('messages.MAudioBody'),
};
const evTypes = {
'm.sticker': sdk.getComponent('messages.MStickerBody'),

View file

@ -129,12 +129,13 @@ export default class ReactionsRowButton extends React.PureComponent {
},
);
}
const isPeeking = room.getMyMembership() !== "join";
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <AccessibleButton
className={classes}
aria-label={label}
onClick={this.onClick}
disabled={isPeeking}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
>

View file

@ -521,11 +521,12 @@ export default class TextualBody extends React.Component {
const LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget');
widgets = this.state.links.map((link)=>{
return <LinkPreviewWidget
key={link}
link={link}
mxEvent={this.props.mxEvent}
onCancelClick={this.onCancelClick}
onHeightChanged={this.props.onHeightChanged} />;
key={link}
link={link}
mxEvent={this.props.mxEvent}
onCancelClick={this.onCancelClick}
onHeightChanged={this.props.onHeightChanged}
/>;
});
}

View file

@ -310,9 +310,14 @@ export default class AliasSettings extends React.Component {
let found = false;
const canonicalValue = this.state.canonicalAlias || "";
const canonicalAliasSection = (
<Field onChange={this.onCanonicalAliasChange} value={canonicalValue}
disabled={this.state.updatingCanonicalAlias || !this.props.canSetCanonicalAlias}
element='select' id='canonicalAlias' label={_t('Main address')}>
<Field
onChange={this.onCanonicalAliasChange}
value={canonicalValue}
disabled={this.state.updatingCanonicalAlias || !this.props.canSetCanonicalAlias}
element='select'
id='canonicalAlias'
label={_t('Main address')}
>
<option value="" key="unset">{ _t('not specified') }</option>
{
this._getAliases().map((alias, i) => {
@ -326,9 +331,9 @@ export default class AliasSettings extends React.Component {
}
{
found || !this.state.canonicalAlias ? '' :
<option value={ this.state.canonicalAlias } key='arbitrary'>
{ this.state.canonicalAlias }
</option>
<option value={ this.state.canonicalAlias } key='arbitrary'>
{ this.state.canonicalAlias }
</option>
}
</Field>
);

View file

@ -205,16 +205,34 @@ export default class RoomProfileSettings extends React.Component {
noValidate={true}
className="mx_ProfileSettings_profileForm"
>
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged} accept="image/*" />
<input
type="file"
ref={this._avatarUpload}
className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged}
accept="image/*"
/>
<div className="mx_ProfileSettings_profile">
<div className="mx_ProfileSettings_controls">
<Field label={_t("Room Name")}
type="text" value={this.state.displayName} autoComplete="off"
onChange={this._onDisplayNameChanged} disabled={!this.state.canSetName} />
<Field className="mx_ProfileSettings_controls_topic" id="profileTopic" label={_t("Room Topic")} disabled={!this.state.canSetTopic}
type="text" value={this.state.topic} autoComplete="off"
onChange={this._onTopicChanged} element="textarea" />
<Field
label={_t("Room Name")}
type="text"
value={this.state.displayName}
autoComplete="off"
onChange={this._onDisplayNameChanged}
disabled={!this.state.canSetName}
/>
<Field
className="mx_ProfileSettings_controls_topic"
id="profileTopic"
label={_t("Room Topic")}
disabled={!this.state.canSetTopic}
type="text"
value={this.state.topic}
autoComplete="off"
onChange={this._onTopicChanged}
element="textarea"
/>
</div>
<AvatarSetting
avatarUrl={this.state.avatarUrl}

View file

@ -68,10 +68,12 @@ export default class UrlPreviewSettings extends React.Component {
if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, "room")) {
previewsForRoom = (
<label>
<SettingsFlag name="urlPreviewsEnabled"
level={SettingLevel.ROOM}
roomId={roomId}
isExplicit={true} />
<SettingsFlag
name="urlPreviewsEnabled"
level={SettingLevel.ROOM}
roomId={roomId}
isExplicit={true}
/>
</label>
);
} else {
@ -91,8 +93,8 @@ export default class UrlPreviewSettings extends React.Component {
const previewsForRoomAccount = ( // in an e2ee room we use a special key to enforce per-room opt-in
<SettingsFlag name={isEncrypted ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'}
level={SettingLevel.ROOM_ACCOUNT}
roomId={roomId} />
level={SettingLevel.ROOM_ACCOUNT}
roomId={roomId} />
);
return (

View file

@ -176,8 +176,11 @@ class EntityTile extends React.Component {
// The wrapping div is required to make the magic mouse listener work, for some reason.
return (
<div ref={(c) => this.container = c} >
<AccessibleButton className={classNames(mainClassNames)} title={this.props.title}
onClick={this.props.onClick}>
<AccessibleButton
className={classNames(mainClassNames)}
title={this.props.title}
onClick={this.props.onClick}
>
<div className="mx_EntityTile_avatar">
{ av }
{ e2eIcon }

View file

@ -128,8 +128,8 @@ export default class LinkPreviewWidget extends React.Component {
let img;
if (image) {
img = <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}>
<img style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={image} onClick={this.onImageClick} />
</div>;
<img style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={image} onClick={this.onImageClick} />
</div>;
}
// The description includes &-encoded HTML entities, we decode those as React treats the thing as an

View file

@ -53,9 +53,9 @@ export default class PinnedEventTile extends React.Component {
if (index !== -1) {
pinned.splice(index, 1);
MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '')
.then(() => {
if (this.props.onUnpinned) this.props.onUnpinned();
});
.then(() => {
if (this.props.onUnpinned) this.props.onUnpinned();
});
} else if (this.props.onUnpinned) this.props.onUnpinned();
}
};
@ -98,8 +98,11 @@ export default class PinnedEventTile extends React.Component {
{ formatFullDate(new Date(this.props.mxEvent.getTs())) }
</span>
<div className="mx_PinnedEventTile_message">
<MessageEvent mxEvent={this.props.mxEvent} className="mx_PinnedEventTile_body" maxImageHeight={150}
onHeightChanged={() => {}} // we need to give this, apparently
<MessageEvent
mxEvent={this.props.mxEvent}
className="mx_PinnedEventTile_body"
maxImageHeight={150}
onHeightChanged={() => {}} // we need to give this, apparently
/>
</div>
</div>

View file

@ -64,10 +64,10 @@ export default class PinnedEventsPanel extends React.Component {
pinnedEvents.getContent().pinned.map((eventId) => {
promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then(
(timeline) => {
const event = timeline.getEvents().find((e) => e.getId() === eventId);
return {eventId, timeline, event};
}).catch((err) => {
(timeline) => {
const event = timeline.getEvents().find((e) => e.getId() === eventId);
return {eventId, timeline, event};
}).catch((err) => {
console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId);
console.error(err);
return null; // return lack of context to avoid unhandled errors
@ -113,10 +113,14 @@ export default class PinnedEventsPanel extends React.Component {
}
return this.state.pinned.map((context) => {
return (<PinnedEventTile key={context.event.getId()}
mxRoom={this.props.room}
mxEvent={context.event}
onUnpinned={this._updatePinnedMessages} />);
return (
<PinnedEventTile
key={context.event.getId()}
mxRoom={this.props.room}
mxEvent={context.event}
onUnpinned={this._updatePinnedMessages}
/>
);
});
}

View file

@ -187,8 +187,7 @@ export default class ReadReceiptMarker extends React.PureComponent {
}
return (
<NodeAnimator
startStyles={this.state.startStyles} >
<NodeAnimator startStyles={this.state.startStyles}>
<MemberAvatar
member={this.props.member}
fallbackUserId={this.props.fallbackUserId}

View file

@ -79,8 +79,13 @@ export default class ReplyPreview extends React.Component {
{ _t('Replying') }
</div>
<div className="mx_ReplyPreview_header mx_ReplyPreview_cancel">
<img className="mx_filterFlipColor" src={require("../../../../res/img/cancel.svg")} width="18" height="18"
onClick={cancelQuoting} />
<img
className="mx_filterFlipColor"
src={require("../../../../res/img/cancel.svg")}
width="18"
height="18"
onClick={cancelQuoting}
/>
</div>
<div className="mx_ReplyPreview_clear" />
<EventTile

View file

@ -88,11 +88,11 @@ export default class RoomDetailRow extends React.Component {
const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
const guestRead = room.worldReadable ? (
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
) : <div />;
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
) : <div />;
const guestJoin = room.guestCanJoin ? (
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
) : <div />;
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
) : <div />;
const perms = (guestRead || guestJoin) ? (<div className="mx_RoomDirectory_perms">
{ guestRead }&nbsp;

View file

@ -539,6 +539,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
onResize={this.props.onResize}
showSkeleton={showSkeleton}
extraTiles={extraTiles}
resizeNotifier={this.props.resizeNotifier}
alwaysVisible={ALWAYS_VISIBLE_TAGS.includes(orderedTagId)}
/>
});

View file

@ -19,6 +19,7 @@ import React, {useState} from "react";
import { _t } from "../../../languageHandler";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
import SpaceStore from "../../../stores/SpaceStore";
const RoomListNumResults: React.FC = () => {
const [count, setCount] = useState<number>(null);
@ -34,7 +35,10 @@ const RoomListNumResults: React.FC = () => {
if (typeof count !== "number") return null;
return <div className="mx_LeftPanel_roomListFilterCount">
{_t("%(count)s results", { count })}
{ SpaceStore.instance.spacePanelSpaces.length
? _t("%(count)s results in all spaces", { count })
: _t("%(count)s results", { count })
}
</div>;
};

View file

@ -44,6 +44,7 @@ import { ActionPayload } from "../../../dispatcher/payloads";
import { Enable, Resizable } from "re-resizable";
import { Direction } from "re-resizable/lib/resizer";
import { polyfillTouchEvent } from "../../../@types/polyfill";
import { ResizeNotifier } from "../../../utils/ResizeNotifier";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
@ -75,7 +76,7 @@ interface IProps {
onResize: () => void;
showSkeleton?: boolean;
alwaysVisible?: boolean;
resizeNotifier: ResizeNotifier;
extraTiles?: ReactComponentElement<typeof ExtraTile>[];
// TODO: Account for https://github.com/vector-im/element-web/issues/14179
@ -528,6 +529,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
tiles.push(<RoomTile
room={room}
key={`room-${room.roomId}`}
resizeNotifier={this.props.resizeNotifier}
showMessagePreview={this.layout.showPreviews}
isMinimized={this.props.isMinimized}
tag={this.props.tagId}

View file

@ -53,12 +53,14 @@ import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/Community
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { getUnsentMessages } from "../../structures/RoomStatusBar";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { ResizeNotifier } from "../../../utils/ResizeNotifier";
interface IProps {
room: Room;
showMessagePreview: boolean;
isMinimized: boolean;
tag: TagID;
resizeNotifier: ResizeNotifier;
}
type PartialDOMRect = Pick<DOMRect, "left" | "bottom">;
@ -102,6 +104,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
};
this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room);
this.roomProps = EchoChamber.forRoom(this.props.room);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.on("middlePanelResized", this.onResize);
}
}
private countUnsentEvents(): number {
@ -116,6 +121,12 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
this.forceUpdate(); // notification state changed - update
};
private onResize = () => {
if (this.showMessagePreview && !this.state.messagePreview) {
this.setState({messagePreview: this.generatePreview()});
}
};
private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => {
if (!room?.roomId === this.props.room.roomId) return;
this.setState({hasUnsentEvents: this.countUnsentEvents() > 0});
@ -195,6 +206,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
);
this.props.room.off("Room.name", this.onRoomNameUpdate);
}
if (this.props.resizeNotifier) {
this.props.resizeNotifier.off("middlePanelResized", this.onResize);
}
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
defaultDispatcher.unregister(this.dispatcherRef);
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);

View file

@ -266,25 +266,25 @@ export default class Stickerpicker extends React.Component {
width: this.popoverWidth,
}}
>
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} zIndex={STICKERPICKER_Z_INDEX}>
<AppTile
app={stickerApp}
room={this.props.room}
fullWidth={true}
userId={MatrixClientPeg.get().credentials.userId}
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
waitForIframeLoad={true}
showMenubar={true}
onEditClick={this._launchManageIntegrations}
onDeleteClick={this._removeStickerpickerWidgets}
showTitle={false}
showCancel={false}
showPopout={false}
onMinimiseClick={this._onHideStickersClick}
handleMinimisePointerEvents={true}
userWidget={true}
/>
</PersistedElement>
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} zIndex={STICKERPICKER_Z_INDEX}>
<AppTile
app={stickerApp}
room={this.props.room}
fullWidth={true}
userId={MatrixClientPeg.get().credentials.userId}
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
waitForIframeLoad={true}
showMenubar={true}
onEditClick={this._launchManageIntegrations}
onDeleteClick={this._removeStickerpickerWidgets}
showTitle={false}
showCancel={false}
showPopout={false}
onMinimiseClick={this._onHideStickersClick}
handleMinimisePointerEvents={true}
userWidget={true}
/>
</PersistedElement>
</div>
</div>
);

View file

@ -27,6 +27,10 @@ import LiveRecordingClock from "../voice_messages/LiveRecordingClock";
import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
import RecordingPlayback from "../voice_messages/RecordingPlayback";
import {MsgType} from "matrix-js-sdk/src/@types/event";
import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog";
import CallMediaHandler from "../../../CallMediaHandler";
interface IProps {
room: Room;
@ -64,8 +68,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
const mxc = await this.state.recorder.upload();
MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
"body": "Voice message",
"msgtype": "org.matrix.msc2516.voice",
//"msgtype": MsgType.Audio,
//"msgtype": "org.matrix.msc2516.voice",
"msgtype": MsgType.Audio,
"url": mxc,
"info": {
duration: Math.round(this.state.recorder.durationSeconds * 1000),
@ -83,10 +87,6 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
},
"org.matrix.msc1767.audio": {
duration: Math.round(this.state.recorder.durationSeconds * 1000),
// TODO: @@ TravisR: Waveform? (MSC1767 decision)
},
"org.matrix.experimental.msc2516.voice": { // MSC2516+MSC1767 experiment
duration: Math.round(this.state.recorder.durationSeconds * 1000),
// Events can't have floats, so we try to maintain resolution by using 1024
// as a maximum value. The waveform contains values between zero and 1, so this
@ -95,6 +95,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
// We're expecting about one data point per second of audio.
waveform: this.state.recorder.getPlayback().waveform.map(v => Math.round(v * 1024)),
},
"org.matrix.msc2516.voice": {}, // No content, this is a rendering hint
});
await this.disposeRecording();
}
@ -115,16 +116,59 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
await this.state.recorder.stop();
return;
}
const recorder = VoiceRecordingStore.instance.startRecording();
await recorder.start();
// We don't need to remove the listener: the recorder will clean that up for us.
recorder.on(UPDATE_EVENT, (ev: RecordingState) => {
if (ev === RecordingState.EndingSoon) return; // ignore this state: it has no UI purpose here
this.setState({recordingPhase: ev});
});
// The "microphone access error" dialogs are used a lot, so let's functionify them
const accessError = () => {
Modal.createTrackedDialog('Microphone Access Error', '', ErrorDialog, {
title: _t("Unable to access your microphone"),
description: <>
<p>{_t(
"We were unable to access your microphone. Please check your browser settings and try again.",
)}</p>
</>,
});
};
this.setState({recorder, recordingPhase: RecordingState.Started});
// Do a sanity test to ensure we're about to grab a valid microphone reference. Things might
// change between this and recording, but at least we will have tried.
try {
const devices = await CallMediaHandler.getDevices();
if (!devices?.['audioinput']?.length) {
Modal.createTrackedDialog('No Microphone Error', '', ErrorDialog, {
title: _t("No microphone found"),
description: <>
<p>{_t(
"We didn't find a microphone on your device. Please check your settings and try again.",
)}</p>
</>,
});
return;
}
// else we probably have a device that is good enough
} catch (e) {
console.error("Error getting devices: ", e);
accessError();
return;
}
try {
const recorder = VoiceRecordingStore.instance.startRecording();
await recorder.start();
// We don't need to remove the listener: the recorder will clean that up for us.
recorder.on(UPDATE_EVENT, (ev: RecordingState) => {
if (ev === RecordingState.EndingSoon) return; // ignore this state: it has no UI purpose here
this.setState({recordingPhase: ev});
});
this.setState({recorder, recordingPhase: RecordingState.Started});
} catch (e) {
console.error("Error starting recording: ", e);
accessError();
// noinspection ES6MissingAwait - if this goes wrong we don't want it to affect the call stack
VoiceRecordingStore.instance.disposeRecording();
}
};
private renderWaveformArea(): ReactNode {

View file

@ -154,7 +154,7 @@ export default class ChangeAvatar extends React.Component {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop'
name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />;
name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />;
}
let uploadSection;

View file

@ -206,7 +206,7 @@ export default class ChangePassword extends React.Component {
test: ({ value, allowEmpty }) => allowEmpty || !!value,
invalid: () => _t("Passwords can't be empty"),
},
],
],
});
onChangeNewPassword = (ev) => {
@ -245,7 +245,7 @@ export default class ChangePassword extends React.Component {
},
invalid: () => _t("Passwords don't match"),
},
],
],
});
onClickChange = async (ev) => {

View file

@ -259,7 +259,7 @@ export default class CrossSigningPanel extends React.PureComponent {
<td>{_t("Homeserver feature support:")}</td>
<td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
</tr>
</tbody></table>
</tbody></table>
</details>
{errorSection}
{actionRow}

View file

@ -214,7 +214,7 @@ export default class DevicesPanel extends React.Component {
const deleteButton = this.state.deleting ?
<Spinner w={22} h={22} /> :
<AccessibleButton onClick={this._onDeleteClick} kind="danger_sm">
{ _t("Delete %(count)s sessions", {count: this.state.selectedDevices.length}) }
{ _t("Delete %(count)s sessions", {count: this.state.selectedDevices.length})}
</AccessibleButton>;
const classes = classNames(this.props.className, "mx_DevicesPanel");

View file

@ -100,7 +100,7 @@ export default class Notifications extends React.Component {
MatrixClientPeg.get().setPushRuleEnabled(
'global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked,
).then(function() {
self._refreshFromServer();
self._refreshFromServer();
});
};
@ -580,12 +580,12 @@ export default class Notifications extends React.Component {
"vectorRuleId": "_keywords",
"description": (
<span>
{ _t('Messages containing <span>keywords</span>',
{},
{ 'span': (sub) =>
<span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>,
},
)}
{ _t('Messages containing <span>keywords</span>',
{},
{ 'span': (sub) =>
<span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>,
},
)}
</span>
),
"vectorState": self.state.vectorContentRules.vectorState,
@ -743,8 +743,8 @@ export default class Notifications extends React.Component {
emailNotificationsRow(address, label) {
return <LabelledToggleSwitch value={this.hasEmailPusher(this.state.pushers, address)}
onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
label={label} key={`emailNotif_${label}`} />;
onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
label={label} key={`emailNotif_${label}`} />;
}
render() {
@ -757,8 +757,8 @@ export default class Notifications extends React.Component {
let masterPushRuleDiv;
if (this.state.masterPushRule) {
masterPushRuleDiv = <LabelledToggleSwitch value={!this.state.masterPushRule.enabled}
onChange={this.onEnableNotificationsChange}
label={_t('Enable notifications for this account')} />;
onChange={this.onEnableNotificationsChange}
label={_t('Enable notifications for this account')} />;
}
let clearNotificationsButton;
@ -874,16 +874,16 @@ export default class Notifications extends React.Component {
{ spinner }
<LabelledToggleSwitch value={SettingsStore.getValue("notificationsEnabled")}
onChange={this.onEnableDesktopNotificationsChange}
label={_t('Enable desktop notifications for this session')} />
onChange={this.onEnableDesktopNotificationsChange}
label={_t('Enable desktop notifications for this session')} />
<LabelledToggleSwitch value={SettingsStore.getValue("notificationBodyEnabled")}
onChange={this.onEnableDesktopNotificationBodyChange}
label={_t('Show message in desktop notification')} />
onChange={this.onEnableDesktopNotificationBodyChange}
label={_t('Show message in desktop notification')} />
<LabelledToggleSwitch value={SettingsStore.getValue("audioNotificationsEnabled")}
onChange={this.onEnableAudioNotificationsChange}
label={_t('Enable audible notifications for this session')} />
onChange={this.onEnableAudioNotificationsChange}
label={_t('Enable audible notifications for this session')} />
{ emailNotificationsRows }

View file

@ -170,8 +170,12 @@ export default class ProfileSettings extends React.Component {
noValidate={true}
className="mx_ProfileSettings_profileForm"
>
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged} accept="image/*" />
<input
type="file"
ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged}
accept="image/*"
/>
<div className="mx_ProfileSettings_profile">
<div className="mx_ProfileSettings_controls">
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span>

View file

@ -90,12 +90,18 @@ export class ExistingEmailAddress extends React.Component {
<span className="mx_ExistingEmailAddress_promptText">
{_t("Remove %(email)s?", {email: this.props.email.address} )}
</span>
<AccessibleButton onClick={this._onActuallyRemove} kind="danger_sm"
className="mx_ExistingEmailAddress_confirmBtn">
<AccessibleButton
onClick={this._onActuallyRemove}
kind="danger_sm"
className="mx_ExistingEmailAddress_confirmBtn"
>
{_t("Remove")}
</AccessibleButton>
<AccessibleButton onClick={this._onDontRemove} kind="link_sm"
className="mx_ExistingEmailAddress_confirmBtn">
<AccessibleButton
onClick={this._onDontRemove}
kind="link_sm"
className="mx_ExistingEmailAddress_confirmBtn"
>
{_t("Cancel")}
</AccessibleButton>
</div>
@ -228,21 +234,28 @@ export default class EmailAddresses extends React.Component {
);
if (this.state.verifying) {
addButton = (
<div>
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
<AccessibleButton onClick={this._onContinueClick} kind="primary"
disabled={this.state.continueDisabled}>
{_t("Continue")}
</AccessibleButton>
</div>
<div>
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
<AccessibleButton
onClick={this._onContinueClick}
kind="primary"
disabled={this.state.continueDisabled}
>
{_t("Continue")}
</AccessibleButton>
</div>
);
}
return (
<div className="mx_EmailAddresses">
{existingEmailElements}
<form onSubmit={this._onAddClick} autoComplete="off"
noValidate={true} className="mx_EmailAddresses_new">
<form
onSubmit={this._onAddClick}
autoComplete="off"
noValidate={true}
className="mx_EmailAddresses_new"
>
<Field
type="text"
label={_t("Email Address")}

View file

@ -85,12 +85,18 @@ export class ExistingPhoneNumber extends React.Component {
<span className="mx_ExistingPhoneNumber_promptText">
{_t("Remove %(phone)s?", {phone: this.props.msisdn.address})}
</span>
<AccessibleButton onClick={this._onActuallyRemove} kind="danger_sm"
className="mx_ExistingPhoneNumber_confirmBtn">
<AccessibleButton
onClick={this._onActuallyRemove}
kind="danger_sm"
className="mx_ExistingPhoneNumber_confirmBtn"
>
{_t("Remove")}
</AccessibleButton>
<AccessibleButton onClick={this._onDontRemove} kind="link_sm"
className="mx_ExistingPhoneNumber_confirmBtn">
<AccessibleButton
onClick={this._onDontRemove}
kind="link_sm"
className="mx_ExistingPhoneNumber_confirmBtn"
>
{_t("Cancel")}
</AccessibleButton>
</div>
@ -246,8 +252,11 @@ export default class PhoneNumbers extends React.Component {
value={this.state.newPhoneNumberCode}
onChange={this._onChangeNewPhoneNumberCode}
/>
<AccessibleButton onClick={this._onContinueClick} kind="primary"
disabled={this.state.continueDisabled}>
<AccessibleButton
onClick={this._onContinueClick}
kind="primary"
disabled={this.state.continueDisabled}
>
{_t("Continue")}
</AccessibleButton>
</form>

View file

@ -80,9 +80,11 @@ export default class GeneralRoomSettingsTab extends React.Component {
flairSection = <>
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
<RelatedGroupSettings roomId={room.roomId}
canSetRelatedGroups={canChangeGroups}
relatedGroupsEvent={groupsEvent} />
<RelatedGroupSettings
roomId={room.roomId}
canSetRelatedGroups={canChangeGroups}
relatedGroupsEvent={groupsEvent}
/>
</div>
</>;
}
@ -97,8 +99,8 @@ export default class GeneralRoomSettingsTab extends React.Component {
<div className="mx_SettingsTab_heading">{_t("Room Addresses")}</div>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
<AliasSettings roomId={this.props.roomId}
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
</div>
<div className="mx_SettingsTab_heading">{_t("Other")}</div>
{ flairSection }

View file

@ -155,7 +155,7 @@ export default class NotificationsSettingsTab extends React.Component {
<div>
<span>{_t("Notification sound")}: <code>{this.state.currentSound}</code></span><br />
<AccessibleButton className="mx_NotificationSound_resetSound" disabled={this.state.currentSound == "default"} onClick={this._clearSound.bind(this)} kind="primary">
{_t("Reset")}
{_t("Reset")}
</AccessibleButton>
</div>
<div>
@ -167,11 +167,11 @@ export default class NotificationsSettingsTab extends React.Component {
{currentUploadedFile}
<AccessibleButton className="mx_NotificationSound_browse" onClick={this._triggerUploader.bind(this)} kind="primary">
{_t("Browse")}
{_t("Browse")}
</AccessibleButton>
<AccessibleButton className="mx_NotificationSound_save" disabled={this.state.uploadedFile == null} onClick={this._onClickSaveSound.bind(this)} kind="primary">
{_t("Save")}
{_t("Save")}
</AccessibleButton>
<br />
</div>

View file

@ -323,8 +323,11 @@ export default class GeneralUserSettingsTab extends React.Component {
return (
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Language and region")}</span>
<LanguageDropdown className="mx_GeneralUserSettingsTab_languageInput"
onOptionChange={this._onLanguageChange} value={this.state.language} />
<LanguageDropdown
className="mx_GeneralUserSettingsTab_languageInput"
onOptionChange={this._onLanguageChange}
value={this.state.language}
/>
</div>
);
}
@ -333,8 +336,10 @@ export default class GeneralUserSettingsTab extends React.Component {
return (
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Spell check dictionaries")}</span>
<SpellCheckSettings languages={this.state.spellCheckLanguages}
onLanguagesChange={this._onSpellCheckLanguagesChange} />
<SpellCheckSettings
languages={this.state.spellCheckLanguages}
onLanguagesChange={this._onSpellCheckLanguagesChange}
/>
</div>
);
}

View file

@ -18,6 +18,7 @@ import React from 'react';
import {_t, getCurrentLanguage} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
import AccessibleTooltipButton from '../../../elements/AccessibleTooltipButton';
import SdkConfig from "../../../../../SdkConfig";
import createRoom from "../../../../../createRoom";
import Modal from "../../../../../Modal";
@ -26,6 +27,9 @@ import PlatformPeg from "../../../../../PlatformPeg";
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import UpdateCheckButton from "../../UpdateCheckButton";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import { copyPlaintext } from "../../../../../utils/strings";
import * as ContextMenu from "../../../../structures/ContextMenu";
import { toRightOf } from "../../../../structures/ContextMenu";
interface IProps {
closeSettingsFn: () => {};
@ -38,6 +42,8 @@ interface IState {
@replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab")
export default class HelpUserSettingsTab extends React.Component<IProps, IState> {
protected closeCopiedTooltip: () => void;
constructor(props) {
super(props);
@ -56,6 +62,12 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
});
}
componentWillUnmount() {
// if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close
// the tooltip otherwise, such as pressing Escape
if (this.closeCopiedTooltip) this.closeCopiedTooltip();
}
private onClearCacheAndReload = (e) => {
if (!PlatformPeg.get()) return;
@ -153,6 +165,20 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
);
}
onAccessTokenCopyClick = async (e) => {
e.preventDefault();
const target = e.target; // copy target before we go async and React throws it away
const successful = await copyPlaintext(MatrixClientPeg.get().getAccessToken());
const buttonRect = target.getBoundingClientRect();
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
this.closeCopiedTooltip = target.onmouseleave = close;
}
render() {
const brand = SdkConfig.get().brand;
@ -269,12 +295,20 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
<div className='mx_SettingsTab_subsectionText'>
{_t("Homeserver is")} <code>{MatrixClientPeg.get().getHomeserverUrl()}</code><br />
{_t("Identity Server is")} <code>{MatrixClientPeg.get().getIdentityServerUrl()}</code><br />
{_t("Access Token:") + ' '}
<AccessibleButton element="span" onClick={this.showSpoiler}
data-spoiler={MatrixClientPeg.get().getAccessToken()}
>
&lt;{ _t("click to reveal") }&gt;
</AccessibleButton>
<br />
<details>
<summary>{_t("Access Token")}</summary><br />
<b>{_t("Your access token gives full access to your account."
+ " Do not share it with anyone." )}</b>
<div className="mx_HelpUserSettingsTab_accessToken">
<code>{MatrixClientPeg.get().getAccessToken()}</code>
<AccessibleTooltipButton
title={_t("Copy")}
onClick={this.onAccessTokenCopyClick}
className="mx_HelpUserSettingsTab_accessToken_copy"
/>
</div>
</details><br />
<div className='mx_HelpUserSettingsTab_debugButton'>
<AccessibleButton onClick={this.onClearCacheAndReload} kind='danger'>
{_t("Clear cache and reload")}

View file

@ -257,12 +257,16 @@ export default class SecurityUserSettingsTab extends React.Component {
const userIds = !ignoredUserIds?.length
? _t('You have no ignored users.')
: ignoredUserIds.map((u) => <IgnoredUser
userId={u}
onUnignored={this._onUserUnignored}
key={u}
inProgress={waitingUnignored.includes(u)}
/>);
: ignoredUserIds.map((u) => {
return (
<IgnoredUser
userId={u}
onUnignored={this._onUserUnignored}
key={u}
inProgress={waitingUnignored.includes(u)}
/>
);
});
return (
<div className='mx_SettingsTab_section'>

View file

@ -176,8 +176,8 @@ export default class VoiceUserSettingsTab extends React.Component {
const defaultDevice = getDefaultDevice(audioOutputs);
speakerDropdown = (
<Field element="select" label={_t("Audio Output")}
value={this.state.activeAudioOutput || defaultDevice}
onChange={this._setAudioOutput}>
value={this.state.activeAudioOutput || defaultDevice}
onChange={this._setAudioOutput}>
{this._renderDeviceOptions(audioOutputs, 'audioOutput')}
</Field>
);
@ -188,8 +188,8 @@ export default class VoiceUserSettingsTab extends React.Component {
const defaultDevice = getDefaultDevice(audioInputs);
microphoneDropdown = (
<Field element="select" label={_t("Microphone")}
value={this.state.activeAudioInput || defaultDevice}
onChange={this._setAudioInput}>
value={this.state.activeAudioInput || defaultDevice}
onChange={this._setAudioInput}>
{this._renderDeviceOptions(audioInputs, 'audioInput')}
</Field>
);
@ -200,8 +200,8 @@ export default class VoiceUserSettingsTab extends React.Component {
const defaultDevice = getDefaultDevice(videoInputs);
webcamDropdown = (
<Field element="select" label={_t("Camera")}
value={this.state.activeVideoInput || defaultDevice}
onChange={this._setVideoInput}>
value={this.state.activeVideoInput || defaultDevice}
onChange={this._setVideoInput}>
{this._renderDeviceOptions(videoInputs, 'videoInput')}
</Field>
);

View file

@ -297,15 +297,19 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
const isActive = activeSpaces.includes(space);
const itemClasses = classNames({
"mx_SpaceItem": true,
"mx_SpaceItem_narrow": isNarrow,
"collapsed": collapsed,
"hasSubSpaces": childSpaces && childSpaces.length,
});
const isInvite = space.getMyMembership() === "invite";
const classes = classNames("mx_SpaceButton", {
mx_SpaceButton_active: isActive,
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
mx_SpaceButton_narrow: isNarrow,
mx_SpaceButton_invite: isInvite,
});
const notificationState = space.getMyMembership() === "invite"
const notificationState = isInvite
? StaticNotificationState.forSymbol("!", NotificationColor.Red)
: SpaceStore.instance.getNotificationState(space.roomId);

View file

@ -29,14 +29,14 @@ export default class VerificationCancelled extends React.Component {
render() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
<p>{_t(
"The other party cancelled the verification.",
)}</p>
<DialogButtons
primaryButton={_t('OK')}
hasCancel={false}
onPrimaryButtonClick={this.props.onDone}
/>
<p>{_t(
"The other party cancelled the verification.",
)}</p>
<DialogButtons
primaryButton={_t('OK')}
hasCancel={false}
onPrimaryButtonClick={this.props.onDone}
/>
</div>;
}
}

View file

@ -15,12 +15,11 @@ limitations under the License.
*/
import React from "react";
import {IRecordingUpdate, VoiceRecording} from "../../../voice/VoiceRecording";
import {IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording} from "../../../voice/VoiceRecording";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {arrayFastResample, arraySeed} from "../../../utils/arrays";
import {percentageOf} from "../../../utils/numbers";
import Waveform from "./Waveform";
import {PLAYBACK_WAVEFORM_SAMPLES} from "../../../voice/Playback";
interface IProps {
recorder: VoiceRecording;
@ -38,14 +37,14 @@ export default class LiveRecordingWaveform extends React.PureComponent<IProps, I
public constructor(props) {
super(props);
this.state = {heights: arraySeed(0, PLAYBACK_WAVEFORM_SAMPLES)};
this.state = {heights: arraySeed(0, RECORDING_PLAYBACK_SAMPLES)};
this.props.recorder.liveData.onUpdate(this.onRecordingUpdate);
}
private onRecordingUpdate = (update: IRecordingUpdate) => {
// The waveform and the downsample target are pretty close, so we should be fine to
// do this, despite the docs on arrayFastResample.
const bars = arrayFastResample(Array.from(update.waveform), PLAYBACK_WAVEFORM_SAMPLES);
const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES);
this.setState({
// The incoming data is between zero and one, but typically even screaming into a
// microphone won't send you over 0.6, so we artificially adjust the gain for the

View file

@ -158,7 +158,7 @@ export default class EditorModel {
}
}
reset(serializedParts: SerializedPart[], caret: Caret, inputType: string) {
reset(serializedParts: SerializedPart[], caret?: Caret, inputType?: string) {
this._parts = serializedParts.map(p => this._partCreator.deserializePart(p));
if (!caret) {
caret = this.getPositionAtEnd();

View file

@ -34,7 +34,7 @@ interface ISerializedPart {
interface ISerializedPillPart {
type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
text: string;
resourceId: string;
resourceId?: string;
}
export type SerializedPart = ISerializedPart | ISerializedPillPart;
@ -287,6 +287,14 @@ abstract class PillPart extends BasePart implements IPillPart {
}
}
serialize(): ISerializedPillPart {
return {
type: this.type,
text: this.text,
resourceId: this.resourceId,
};
}
get canEdit() {
return false;
}
@ -366,6 +374,13 @@ class AtRoomPillPart extends RoomPillPart {
get type(): IPillPart["type"] {
return Type.AtRoomPill;
}
serialize(): ISerializedPillPart {
return {
type: this.type,
text: this.text,
};
}
}
class UserPillPart extends PillPart {
@ -394,14 +409,6 @@ class UserPillPart extends PillPart {
get className() {
return "mx_UserPill mx_Pill";
}
serialize(): ISerializedPillPart {
return {
type: this.type,
text: this.text,
resourceId: this.resourceId,
};
}
}
class PillCandidatePart extends PlainBasePart implements IPillCandidatePart {
@ -495,7 +502,7 @@ export class PartCreator {
case Type.PillCandidate:
return this.pillCandidate(part.text);
case Type.RoomPill:
return this.roomPill(part.text);
return this.roomPill(part.resourceId);
case Type.UserPill:
return this.userPill(part.text, part.resourceId);
}

View file

@ -61,9 +61,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} =
// const inlinePattern = "(?:^|\\s)(?<!\\\\)\\$(?!\\s)(([^$]|\\\\\\$)+?)(?<!\\\\|\\s)\\$";
// conditions for display math detection $$...$$:
// - pattern starts at beginning of line or is not prefixed with backslash or dollar
// - pattern starts and ends on a new line
// - left delimiter ($$) is not escaped by backslash
"display": "(^|[^\\\\$])\\$\\$(([^$]|\\\\\\$)+?)\\$\\$",
"display": "(^)\\$\\$(([^$]|\\\\\\$)+?)\\$\\$$",
// conditions for inline math detection $...$:
// - pattern starts at beginning of line, follows whitespace character or punctuation
@ -78,9 +78,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} =
// detect math with latex delimiters, inline: \(...\), display \[...\]
// conditions for display math detection \[...\]:
// - pattern starts at beginning of line or is not prefixed with backslash
// - pattern starts and ends on a new line
// - pattern is not empty
"display": "(^|[^\\\\])\\\\\\[(?!\\\\\\])(.*?)\\\\\\]",
"display": "(^)\\\\\\[(?!\\\\\\])(.*?)\\\\\\]$",
// conditions for inline math detection \(...\):
// - pattern starts at beginning of line or is not prefixed with backslash

View file

@ -323,7 +323,7 @@
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s odstranil(a) své zobrazované jméno (%(oldDisplayName)s).",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s zrušil(a) pozvání pro uživatele %(targetName)s.",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy, a to od chvíle jejich pozvání.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy, a to od chvíle jejich vstupu.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy od chvíle jejich vstupu.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv pro kohokoliv.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s změnil(a) připíchnuté zprávy této místnosti.",
@ -744,7 +744,7 @@
"e.g. <CurrentPageURL>": "např. <CurrentPageURL>",
"Your device resolution": "Rozlišení obrazovky vašeho zařízení",
"The information being sent to us to help make %(brand)s better includes:": "Abychom mohli %(brand)s zlepšovat, posíláte nám následující informace:",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "V případě, že se na stránce vyskytují identifikační údaje, jako například název místnosti, ID uživatele, místnosti a nebo skupiny, jsou tyto údaje před odesláním na server odstraněny.",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Pokud tato stránka obsahuje identifikovatelné údaje, například ID místnosti, uživatele nebo skupiny, jsou tyto údaje před odesláním na server odstraněny.",
"Call in Progress": "Probíhající hovor",
"A call is currently being placed!": "Právě probíhá jiný hovor!",
"A call is already in progress!": "Jeden hovor už probíhá!",
@ -896,7 +896,7 @@
"Prompt before sending invites to potentially invalid matrix IDs": "Potvrdit odeslání pozvánky potenciálně neplatným Matrix ID",
"Show avatar changes": "Zobrazovat změny avatarů",
"Show join/leave messages (invites/kicks/bans unaffected)": "Zobrazovat zprávy o vstupu/opuštění (netýká se pozvánek/vykopnutí/vykázání)",
"Show a placeholder for removed messages": "Zobrazovat smazané zprávy jako zamazané",
"Show a placeholder for removed messages": "Zobrazovat smazané zprávy",
"Show display name changes": "Zobrazovat změny zobrazovaného jména",
"Messages containing my username": "Zprávy obsahující moje uživatelské jméno",
"Messages containing @room": "Zprávy obsahující @room",
@ -990,7 +990,7 @@
"Straight rows of keys are easy to guess": "Řádky na klávesnici je moc jednoduché uhodnout",
"Short keyboard patterns are easy to guess": "Krátké sekvence kláves je moc jednoduché uhodnout",
"There was an error joining the room": "Při vstupu do místnosti došlo k chybě",
"Sorry, your homeserver is too old to participate in this room.": "Jejda, váš domovský server je moc zastaralý abyste do této místnosti mohli vstoupit.",
"Sorry, your homeserver is too old to participate in this room.": "Omlouváme se, ale váš domovský server je příliš zastaralý pro vstup do této místnosti.",
"Please contact your homeserver administrator.": "Kontaktujte prosím správce domovského serveru.",
"The other party cancelled the verification.": "Druhá strana ověření zrušila.",
"Verified!": "Ověřeno!",
@ -1130,9 +1130,9 @@
"Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Bez nastavení bezpečného obnovení zpráv přijdete po odhlášení o historii šifrované komunikace.",
"Show a reminder to enable Secure Message Recovery in encrypted rooms": "V šifrovaných konverzacích zobrazovat upozornění na možnost aktivovat bezpečné obnovení zpráv",
"Gets or sets the room topic": "Nastaví nebo zjistí téma místnosti",
"Forces the current outbound group session in an encrypted room to be discarded": "Zahodí aktuálně používanou kryptografickou session na odchozí zprávy",
"Forces the current outbound group session in an encrypted room to be discarded": "Vynutí zahození aktuálně používané relace skupiny v zašifrované místnosti",
"Custom user status messages": "Vlastní statusy",
"Group & filter rooms by custom tags (refresh to apply changes)": "Skupinkování a filtování místností podle štítků (vyžaduje znovunačtení stránky)",
"Group & filter rooms by custom tags (refresh to apply changes)": "Seskupování a filtrování místností podle vlastních štítků (vyžaduje znovunačtení stránky)",
"Render simple counters in room header": "Zobrazovat stavová počítadla v hlavičce místnosti",
"Enable Community Filter Panel": "Povolit panel Filtr skupiny",
"Show developer tools": "Zobrazit nástroje pro vývojáře",
@ -1205,7 +1205,7 @@
"Remove messages": "Mazat zprávy",
"Notify everyone": "Oznámení pro celou místnost",
"Enable encryption?": "Povolit šifrování?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Po zapnutí už nelze šifrování v této místnosti vypnout. Zprávy v šifrovaných místnostech mohou číst jenom členové místnosti, server se k obsahu nedostane. Šifrování místností nepodporuje většina botů a propojení. <a>Více informací o šifrování.</a>",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Po zapnutí již nelze šifrování v této místnosti vypnout. Zprávy v šifrovaných místnostech mohou číst jen členové místnosti, server se k obsahu nedostane. Šifrování místností nepodporuje většina botů a propojení. <a>Více informací o šifrování.</a>",
"Error updating main address": "Nepovedlo se změnit hlavní adresu",
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Nastala chyba při pokusu o nastavení hlavní adresy místnosti. Mohl to zakázat server, nebo to může být dočasná chyba.",
"Power level": "Úroveň oprávnění",
@ -1219,7 +1219,7 @@
"Your Matrix account on %(serverName)s": "Váš účet Matrix na serveru %(serverName)s",
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Zda používáte funkci „breadcrumb“ (ikony nad seznamem místností)",
"Replying With Files": "Odpovídání souborem",
"At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Aktuálně nelze odpovědět souborem. Chcete soubor nahrát a poslat bez odpovídání?",
"At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "V tuto chvíli není možné odpovědět souborem. Chcete tento soubor nahrát bez odpovědi?",
"The file '%(fileName)s' failed to upload.": "Soubor '%(fileName)s' se nepodařilo nahrát.",
"The server does not support the room version specified.": "Server nepodporuje určenou verzi místnosti.",
"Name or Matrix ID": "Jméno nebo Matrix ID",
@ -1354,12 +1354,12 @@
"Unexpected error resolving identity server configuration": "Chyba při hledání konfigurace serveru identity",
"Use lowercase letters, numbers, dashes and underscores only": "Používejte pouze malá písmena, čísla, pomlčky a podtržítka",
"Cannot reach identity server": "Nelze se připojit k serveru identity",
"You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete se zaregistrovat, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje pořád, tak zkontrolujte svojí konfiguraci a nebo kontaktujte správce serveru.",
"You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete si změnit heslo, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje pořád, tak zkontrolujte svojí konfiguraci a nebo kontaktujte správce serveru.",
"You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete se přihlásit, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje pořád, tak zkontrolujte svojí konfiguraci a nebo kontaktujte správce serveru.",
"You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete se zaregistrovat, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje i nadále, zkontrolujte svojí konfiguraci nebo kontaktujte správce serveru.",
"You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete si změnit heslo, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se toto varování zobrazuje i nadále, zkontrolujte svojí konfiguraci nebo kontaktujte správce serveru.",
"You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete se přihlásit, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje i nadále, zkontrolujte svojí konfiguraci nebo kontaktujte správce serveru.",
"Call failed due to misconfigured server": "Volání selhalo, protože je rozbitá konfigurace serveru",
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "Zeptejte se správce (<code>%(homeserverDomain)s</code>) jestli by nemohl nakonfigurovat server TURN, aby začalo fungoval volání.",
"Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Případně můžete zkusit použít veřejný server <code>turn.matrix.org</code>, což nemusí fungovat tak spolehlivě a řekne to tomu cizímu serveru vaší IP adresu. Můžete to udělat v Nastavení.",
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "Požádejte správce svého homeserveru (<code>%(homeserverDomain)s</code>) jestli by nemohl nakonfigurovat TURN server, aby volání fungovala spolehlivě.",
"Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Můžete také zkusit použít veřejný server na adrese <code>turn.matrix.org</code>, ale ten nebude tak spolehlivý a bude sdílet vaši IP adresu s tímto serverem. To můžete spravovat také v Nastavení.",
"Try using turn.matrix.org": "Zkuste použít turn.matrix.org",
"Messages": "Zprávy",
"Actions": "Akce",
@ -1368,7 +1368,7 @@
"Changes the avatar of the current room": "Změní avatar této místnosti",
"Changes your avatar in all rooms": "Změní váš avatar pro všechny místnosti",
"Use an identity server": "Používat server identit",
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Použít server identit k odeslání e-mailové pozvánky. Pokračováním použijete výchozí server identit (%(defaultIdentityServerName)s) nebo ho můžete změnit v Nastavení.",
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "K pozvání e-mailem použijte server identit. Pokračováním použijete výchozí server identit (%(defaultIdentityServerName)s) nebo ho můžete změnit v Nastavení.",
"Use an identity server to invite by email. Manage in Settings.": "Použít server identit na odeslání e-mailové pozvánky. Můžete spravovat v Nastavení.",
"Displays list of commands with usages and descriptions": "Zobrazuje seznam příkazu s popiskem",
"%(senderName)s made no change.": "%(senderName)s neudělal žádnou změnu.",
@ -1708,7 +1708,7 @@
"Backup has a <validity>invalid</validity> signature from this user": "Záloha má <validity>neplatný</validity> podpis od tohoto uživatele",
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Záloha je podepsaná <verify>neznámým</verify> uživatelem %(deviceId)s",
"Close preview": "Zavřít náhled",
"Hide verified sessions": "Schovat ověřené relace",
"Hide verified sessions": "Skrýt ověřené relace",
"%(count)s verified sessions|other": "%(count)s ověřených relací",
"%(count)s verified sessions|one": "1 ověřená relace",
"Language Dropdown": "Menu jazyků",
@ -1721,8 +1721,8 @@
"Unknown (user, session) pair:": "Neznámý pár (uživatel, relace):",
"Session already verified!": "Relace je už ověřená!",
"WARNING: Session already verified, but keys do NOT MATCH!": "VAROVÁNÍ: Relace je už ověřená, ale klíče NEODPOVÍDAJÍ!",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "VAROVÁNÍ: OVĚŘENÍ KLÍČŮ SELHALO! Podpisový klíč pro uživatele %(userId)s a relaci %(deviceId)s je „%(fprint)s“, což neodpovídá klíči „%(fingerprint)s“. Může to znamenat, že je vaše komunikace rušena!",
"The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "Zadaný podpisový klíč odpovídá klíči relace %(deviceId)s od uživatele %(userId)s. Relace byla označena za platnou.",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "VAROVÁNÍ: OVĚŘENÍ KLÍČE SE NEZDAŘILO! Podpisový klíč pro uživatele %(userId)s a relaci %(deviceId)s je „%(fprint)s“, což neodpovídá klíči „%(fingerprint)s“. To by mohlo znamenat, že vaše komunikace je zachycována!",
"The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "Zadaný podpisový klíč odpovídá klíči relace %(deviceId)s od uživatele %(userId)s. Relace byla označena jako ověřená.",
"a few seconds ago": "před pár vteřinami",
"about a minute ago": "před minutou",
"%(num)s minutes ago": "před %(num)s minutami",
@ -1917,8 +1917,8 @@
"Compare unique emoji": "Porovnejte jedinečnou kombinaci emoji",
"Compare a unique set of emoji if you don't have a camera on either device": "Pokud na žádném zařízení nemáte kameru, porovnejte jedinečnou kombinaci emoji",
"Not Trusted": "Nedůvěryhodné",
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) se přihlásil do nové relace a neověřil ji:",
"Ask this user to verify their session, or manually verify it below.": "Poproste tohoto uživatele aby svojí relaci ověřil a nebo jí níže můžete ověřit manuálně.",
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) se přihlásil do nové relace bez ověření:",
"Ask this user to verify their session, or manually verify it below.": "Požádejte tohoto uživatele, aby ověřil svou relaci, nebo jí níže můžete ověřit manuálně.",
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "Relace, kterou se snažíte ověřit, neumožňuje ověření QR kódem ani pomocí emoji, což je to, co %(brand)s podporuje. Zkuste použít jiného klienta.",
"Verify by scanning": "Ověřte naskenováním",
"You declined": "Odmítli jste",
@ -1936,7 +1936,7 @@
"If disabled, messages from encrypted rooms won't appear in search results.": "Když je to zakázané, zprávy v šifrovaných místnostech se nebudou objevovat ve výsledcích vyhledávání.",
"Disable": "Zakázat",
"%(brand)s is securely caching encrypted messages locally for them to appear in search results:": "%(brand)s si bezpečně uchovává šifrované zprávy lokálně, aby v nich mohl vyhledávat:",
"Space used:": "Použitý prostor:",
"Space used:": "Použité místo:",
"Indexed messages:": "Indexované zprávy:",
"Indexed rooms:": "Indexované místnosti:",
"Message downloading sleep time(ms)": "Čas na stažení zprávy (ms)",
@ -2032,8 +2032,8 @@
"Could not find user in room": "Nepovedlo se najít uživatele v místnosti",
"Please supply a widget URL or embed code": "Zadejte prosím URL widgetu nebo jeho kód",
"Send a bug report with logs": "Zaslat hlášení o chybě",
"You signed in to a new session without verifying it:": "Přihlásili jste se do nové relace, ale neoveřili jste ji:",
"Verify your other session using one of the options below.": "Ověřte ostatní relací jedním z následujících způsobů.",
"You signed in to a new session without verifying it:": "Přihlásili jste se do nové relace, aniž byste ji ověřili:",
"Verify your other session using one of the options below.": "Ověřte další relaci jedním z následujících způsobů.",
"Click the button below to confirm deleting these sessions.|other": "Zmáčknutím tlačítka potvrdíte smazání těchto relací.",
"Click the button below to confirm deleting these sessions.|one": "Zmáčknutím tlačítka potvrdíte smazání této relace.",
"Delete sessions|other": "Smazat relace",
@ -2093,7 +2093,7 @@
"Please verify the room ID or address and try again.": "Ověřte prosím, že ID místnosti je správné a zkuste to znovu.",
"Room ID or address of ban list": "ID nebo adresa seznamu zablokovaných",
"Help us improve %(brand)s": "Pomozte nám zlepšovat %(brand)s",
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Zasílat <UsageDataLink>anonymní data o použití aplikace</UsageDataLink>, která nám pomáhají %(brand)s zlepšovat. Bedeme na to používat <PolicyLink>soubory cookie</PolicyLink>.",
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Zasílat <UsageDataLink>anonymní údaje o použití aplikace</UsageDataLink>, která nám pomáhají %(brand)s zlepšovat. K tomu se použije <PolicyLink>soubor cookie</PolicyLink>.",
"I want to help": "Chci pomoci",
"Your homeserver has exceeded its user limit.": "Na vašem domovském serveru byl překročen limit počtu uživatelů.",
"Your homeserver has exceeded one of its resource limits.": "Na vašem domovském serveru byl překročen limit systémových požadavků.",
@ -2166,8 +2166,8 @@
"Signature upload failed": "Podpis se nepodařilo nahrát",
"If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Je-li jiná verze programu %(brand)s stále otevřená na jiné kartě, tak ji prosím zavřete, neboť užívání programu %(brand)s stejným hostitelem se zpožděným nahráváním současně povoleným i zakázaným bude působit problémy.",
"Unexpected server error trying to leave the room": "Neočekávaná chyba serveru při odcházení z místnosti",
"The person who invited you already left the room.": "Uživatel který vás pozval už místnosti není.",
"The person who invited you already left the room, or their server is offline.": "Uživatel který vás pozvat už odešel z místnosti a nebo je jeho server offline.",
"The person who invited you already left the room.": "Uživatel, který vás pozval, již opustil místnost.",
"The person who invited you already left the room, or their server is offline.": "Uživatel, který vás pozval, již opustil místnost nebo je jeho server offline.",
"You left the call": "Odešli jste z hovoru",
"%(senderName)s left the call": "%(senderName)s opustil/a hovor",
"Call ended": "Hovor skončil",
@ -2954,13 +2954,13 @@
"Mobile experience": "Zážitek na mobilních zařízeních",
"Element Web is currently experimental on mobile. The native apps are recommended for most people.": "Element Web je v současné době experimentální na mobilních zařízeních. Nativní aplikace se doporučují pro většinu lidí.",
"Use app for a better experience": "Pro lepší zážitek použijte aplikaci",
"Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web je experimentální na mobilních zařízeních. Pro lepší zážitek a nejnovější funkce použijte naši bezplatnou nativní aplikaci.",
"Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web je v mobilní verzi experimentální. Chcete-li získat lepší zážitek a nejnovější funkce, použijte naši bezplatnou nativní aplikaci.",
"Use app": "Použijte aplikaci",
"Something went wrong in confirming your identity. Cancel and try again.": "Při ověřování vaší identity se něco pokazilo. Zrušte to a zkuste to znovu.",
"Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Váš domovský server odmítl váš pokus o přihlášení. Může to být způsobeno tím, že věci trvají příliš dlouho. Prosím zkuste to znovu. Pokud to bude pokračovat, obraťte se na správce domovského serveru.",
"Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Váš domovský server odmítl váš pokus o přihlášení. To může to být způsobeno tím, že vše trvá příliš dlouho. Zkuste to prosím znovu. Pokud tento problém přetrvává, obraťte se na správce domovského serveru.",
"Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Váš domovský server nebyl dosažitelný a nemohl vás přihlásit. Zkuste to prosím znovu. Pokud to bude pokračovat, obraťte se na správce domovského serveru.",
"Try again": "Zkuste to znovu",
"We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Požádali jsme prohlížeč, aby si pamatoval, který domovský server používáte k přihlášení, ale váš prohlížeč to bohužel zapomněl. Přejděte na přihlašovací stránku a zkuste to znovu.",
"We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Požádali jsme prohlížeč, aby si zapamatoval, který domovský server používáte k přihlášení, ale váš prohlížeč to bohužel zapomněl. Přejděte na přihlašovací stránku a zkuste to znovu.",
"We couldn't log you in": "Nemohli jsme vás přihlásit",
"Show stickers button": "Tlačítko Zobrazit nálepky",
"Windows": "Okna",
@ -3012,8 +3012,8 @@
"Inviting...": "Pozvání...",
"Invite by username": "Pozvat podle uživatelského jména",
"Invite your teammates": "Pozvěte své spolupracovníky",
"Failed to invite the following users to your space: %(csvUsers)s": "Nepodařilo se pozvat následující uživatele do vašeho space: %(csvUsers)s",
"A private space for you and your teammates": "Soukromý space pro Vás a vaše spolupracovníky",
"Failed to invite the following users to your space: %(csvUsers)s": "Nepodařilo se pozvat následující uživatele do vašeho prostoru: %(csvUsers)s",
"A private space for you and your teammates": "Soukromý prostor pro Vás a vaše spolupracovníky",
"Me and my teammates": "Já a moji spolupracovníci",
"A private space just for you": "Soukromý space právě pro vás",
"Just Me": "Pouze já",
@ -3022,7 +3022,7 @@
"At the moment only you can see it.": "V tuto chvíli to vidíte jen Vy.",
"Creating rooms...": "Vytváření místností...",
"Skip for now": "Prozatím přeskočit",
"Failed to create initial space rooms": "Vytvoření počátečních místností ve space se nezdařilo",
"Failed to create initial space rooms": "Vytvoření počátečních místností v prostoru se nezdařilo",
"Random": "Náhodný",
"Your private space <name/>": "Váš soukromý space <name/>",
"Your public space <name/>": "Váš veřejný space <name/>",
@ -3030,9 +3030,9 @@
"<inviter/> invited you to <name/>": "<inviter/> vás pozval do <name/>",
"%(count)s members|one": "%(count)s člen",
"%(count)s members|other": "%(count)s členů",
"Your server does not support showing space hierarchies.": "Váš server nepodporuje zobrazování hierarchií spaces.",
"Your server does not support showing space hierarchies.": "Váš server nepodporuje zobrazování hierarchií prostorů.",
"Default Rooms": "Výchozí místnosti",
"Add existing rooms & spaces": "Přidat stávající místnosti a spaces",
"Add existing rooms & spaces": "Přidat stávající místnosti a prostory",
"Accept Invite": "Přijmout pozvání",
"Find a room...": "Najít místnost...",
"Manage rooms": "Spravovat místnosti",
@ -3044,68 +3044,68 @@
"Remove from Space": "Odebrat ze space",
"Undo": "Vrátit",
"Your message wasn't sent because this homeserver has been blocked by it's administrator. Please <a>contact your service administrator</a> to continue using the service.": "Vaše zpráva nebyla odeslána, protože tento domovský server byl zablokován jeho správcem. <a>Kontaktujte svého správce služby</a>, abyste mohli službu nadále používat.",
"Are you sure you want to leave the space '%(spaceName)s'?": "Opravdu chcete opustit space '%(spaceName)s'?",
"This space is not public. You will not be able to rejoin without an invite.": "Tento space není veřejný. Bez pozvánky se nebudete moci znovu připojit.",
"Are you sure you want to leave the space '%(spaceName)s'?": "Opravdu chcete opustit prostor '%(spaceName)s'?",
"This space is not public. You will not be able to rejoin without an invite.": "Tento prostor není veřejný. Bez pozvánky se nebudete moci znovu připojit.",
"Start audio stream": "Zahájit audio přenos",
"Failed to start livestream": "Nepodařilo spustit živý přenos",
"Unable to start audio streaming.": "Nelze spustit streamování zvuku.",
"View dev tools": "Zobrazit nástroje pro vývojáře",
"Leave Space": "Opustit space",
"Make this space private": "Nastavit tento space jako soukromý",
"Edit settings relating to your space.": "Upravte nastavení týkající se vašeho space.",
"Space settings": "Nastavení space",
"Failed to save space settings.": "Nastavení space se nepodařilo uložit.",
"Invite someone using their name, username (like <userId/>) or <a>share this space</a>.": "Pozvěte někoho pomocí jeho jména, uživatelského jména (například <userId/>) nebo <a>sdílejte tento space</a>.",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Pozvěte někoho pomocí jeho jména, e-mailové adresy, uživatelského jména (například <userId/>) nebo <a>sdílejte tento space</a>.",
"Unnamed Space": "Nejmenovaný space",
"Leave Space": "Opustit prostor",
"Make this space private": "Nastavit tento prostor jako soukromý",
"Edit settings relating to your space.": "Upravte nastavení týkající se vašeho prostoru.",
"Space settings": "Nastavení prostoru",
"Failed to save space settings.": "Nastavení prostoru se nepodařilo uložit.",
"Invite someone using their name, username (like <userId/>) or <a>share this space</a>.": "Pozvěte někoho pomocí jeho jména, uživatelského jména (například <userId/>) nebo <a>sdílejte tento prostor</a>.",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Pozvěte někoho pomocí jeho jména, e-mailové adresy, uživatelského jména (například <userId/>) nebo <a>sdílejte tento prostor</a>.",
"Unnamed Space": "Nejmenovaný prostor",
"Invite to %(spaceName)s": "Pozvat do %(spaceName)s",
"Failed to add rooms to space": "Nepodařilo se přidat místnosti do space",
"Failed to add rooms to space": "Nepodařilo se přidat místnosti do prostoru",
"Applying...": "Potvrzuji...",
"Apply": "Použít",
"Create a new room": "Vytvořit novou místnost",
"Don't want to add an existing room?": "Nechcete přidat existující místnost?",
"Spaces": "Spaces",
"Filter your rooms and spaces": "Filtrujte své místnosti a spaces",
"Spaces": "Prostory",
"Filter your rooms and spaces": "Filtrujte své místnosti a prostory",
"Add existing spaces/rooms": "Přidat existující space/místnost",
"Space selection": "Výběr space",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the space it will be impossible to regain privileges.": "Tuto změnu nebudete moci vrátit zpět, protože budete degradováni, pokud jste posledním privilegovaným uživatelem v daném space, nebude možné znovu získat oprávnění.",
"Space selection": "Výběr prostoru",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the space it will be impossible to regain privileges.": "Tuto změnu nebudete moci vrátit zpět, protože budete degradováni, pokud jste posledním privilegovaným uživatelem v daném prostoru, nebude možné znovu získat oprávnění.",
"Empty room": "Prázdná místnost",
"Suggested Rooms": "Doporučené místnosti",
"Explore space rooms": "Prozkoumat místnosti space",
"You do not have permissions to add rooms to this space": "Nemáte oprávnění k přidávání místností do tohoto space",
"You do not have permissions to add rooms to this space": "Nemáte oprávnění k přidávání místností do tohoto prostoru",
"Add existing room": "Přidat existující místnost",
"You do not have permissions to create new rooms in this space": "Nemáte oprávnění k vytváření nových místností v tomto space",
"You do not have permissions to create new rooms in this space": "Nemáte oprávnění k vytváření nových místností v tomto prostoru",
"Send message": "Poslat zprávu",
"Invite to this space": "Pozvat do tohoto space",
"Invite to this space": "Pozvat do tohoto prostoru",
"Your message was sent": "Zpráva byla odeslána",
"Encrypting your message...": "Šifrování zprávy...",
"Sending your message...": "Odesílání zprávy...",
"Spell check dictionaries": "Slovníky pro kontrolu pravopisu",
"Space options": "Nastavení space",
"Space options": "Nastavení prostoru",
"Space Home": "Domov space",
"New room": "Nová místnost",
"Leave space": "Opusit space",
"Leave space": "Opusit prostor",
"Invite people": "Pozvat lidi",
"Share your public space": "Sdílejte svůj veřejný space",
"Share your public space": "Sdílejte svůj veřejný prostor",
"Invite members": "Pozvat členy",
"Invite by email or username": "Pozvěte e-mailem nebo uživatelským jménem",
"Share invite link": "Sdílet odkaz na pozvánku",
"Click to copy": "Kliknutím zkopírujte",
"Expand space panel": "Rozbalit space panel",
"Collapse space panel": "Sbalit space panel",
"Expand space panel": "Rozbalit panel prostoru",
"Collapse space panel": "Sbalit panel prostoru",
"Creating...": "Vytváření...",
"You can change these at any point.": "Můžete je kdykoli změnit.",
"Give it a photo, name and description to help you identify it.": "Přiřaďte mu obrázek, jméno a popis, abyste jej mohli lépe identifikovat.",
"Your private space": "Váš soukromý space",
"Your public space": "Váš veřejný space",
"Your private space": "Váš soukromý prostor",
"Your public space": "Váš veřejný prostor",
"You can change this later": "Toto můžete změnit později",
"Invite only, best for yourself or teams": "Pouze pozvat, nejlepší pro sebe nebo pro týmy",
"Private": "Soukromý",
"Open space for anyone, best for communities": "Otevřený space pro kohokoli, nejlepší pro komunity",
"Open space for anyone, best for communities": "Otevřený prostor pro kohokoli, nejlepší pro komunity",
"Public": "Veřejný",
"Create a space": "Vytvořit space",
"Create a space": "Vytvořit prostor",
"Jump to the bottom of the timeline when you send a message": "Při odesílání zprávy přeskočit na konec časové osy",
"Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spaces prototyp. Nejsou kompatibilní se skupinami, skupinami v2 a vlastními štítky. Pro některé funkce je vyžadován kompatibilní domovský server.",
"Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Prototyp prostorů. Nejsou kompatibilní se skupinami, skupinami v2 a vlastními štítky. Pro některé funkce je vyžadován kompatibilní domovský server.",
"This homeserver has been blocked by its administrator.": "Tento domovský server byl zablokován jeho správcem.",
"You're already in a call with this person.": "S touto osobou již telefonujete.",
"Already in call": "Již máte hovor",
@ -3123,13 +3123,13 @@
"We'll create rooms for each of them. You can add more later too, including already existing ones.": "Pro každého z nich vytvoříme místnost. Později můžete přidat další, včetně již existujících.",
"Let's create a room for each of them. You can add more later too, including already existing ones.": "Vytvořme místnost pro každého z nich. Později můžete přidat i další, včetně již existujících.",
"Make sure the right people have access. You can invite more later.": "Zajistěte přístup pro správné lidi. Další můžete pozvat později.",
"A private space to organise your rooms": "Soukromý space pro uspořádání vašich místností",
"A private space to organise your rooms": "Soukromý prostor pro uspořádání vašich místností",
"Make sure the right people have access to %(name)s": "Zajistěte, aby do %(name)s měli přístup správní lidé",
"Go to my first room": "Jít do mé první místnosti",
"It's just you at the moment, it will be even better with others.": "V tuto chvíli to jste jen vy, s ostatními to bude ještě lepší.",
"Share %(name)s": "Sdílet %(name)s",
"Private space": "Soukromý space",
"Public space": "Veřejný space",
"Private space": "Soukromý prostor",
"Public space": "Veřejný prostor",
"<inviter/> invites you": "<inviter/> vás zve",
"Search names and description": "Prohledat jména a popisy",
"Create room": "Vytvořit místnost",
@ -3139,10 +3139,10 @@
"Mark as not suggested": "Označit jako nedoporučené",
"Removing...": "Odebírání...",
"Failed to remove some rooms. Try again later": "Odebrání některých místností se nezdařilo. Zkuste to později znovu",
"%(count)s rooms and 1 space|one": "%(count)s místnost a 1 space",
"%(count)s rooms and 1 space|other": "%(count)s místností a 1 space",
"%(count)s rooms and %(numSpaces)s spaces|one": "%(count)s místnost a %(numSpaces)s space",
"%(count)s rooms and %(numSpaces)s spaces|other": "%(count)s místností a %(numSpaces)s spaces",
"%(count)s rooms and 1 space|one": "%(count)s místnost a 1 prostor",
"%(count)s rooms and 1 space|other": "%(count)s místností a 1 prostor",
"%(count)s rooms and %(numSpaces)s spaces|one": "%(count)s místnost a %(numSpaces)s prostorů",
"%(count)s rooms and %(numSpaces)s spaces|other": "%(count)s místností a %(numSpaces)s prostorů",
"If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.": "Pokud nemůžete najít místnost, kterou hledáte, požádejte o pozvánku nebo <a>vytvořte novou místnost</a>.",
"This room is suggested as a good one to join": "Tato místnost je doporučena jako dobrá pro připojení",
"Suggested": "Doporučeno",
@ -3156,7 +3156,7 @@
"Invite with email or username": "Pozvěte e-mailem nebo uživatelským jménem",
"You can change these anytime.": "Tyto údaje můžete kdykoli změnit.",
"Add some details to help people recognise it.": "Přidejte několik podrobností, aby to lidé lépe rozpoznali.",
"Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.": "Spaces jsou nový způsob, jak seskupovat místnosti a lidi. Chcete-li se připojit ke stávajícímu space, budete potřebovat pozvánku.",
"Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.": "Prostory jsou nový způsob, jak seskupovat místnosti a lidi. Chcete-li se připojit ke stávajícímu prostoru, budete potřebovat pozvánku.",
"A new login is accessing your account: %(name)s (%(deviceID)s) at %(ip)s": "K vašemu účtu přistupuje nové přihlášení: %(name)s (%(deviceID)s) pomocí %(ip)s",
"From %(deviceName)s (%(deviceId)s) at %(ip)s": "Z %(deviceName)s (%(deviceId)s) pomocí %(ip)s",
"Verify this login to access your encrypted messages and prove to others that this login is really you.": "Ověřte toto přihlášení, abyste získali přístup k šifrovaným zprávám a dokázali ostatním, že jste to opravdu vy.",
@ -3202,5 +3202,28 @@
"What are some things you want to discuss in %(spaceName)s?": "O kterých tématech chcete diskutovat v %(spaceName)s?",
"Let's create a room for each of them.": "Vytvořme pro každé z nich místnost.",
"Use another login": "Použijte jiné přihlašovací jméno",
"Without verifying, you wont have access to all your messages and may appear as untrusted to others.": "Bez ověření nebudete mít přístup ke všem svým zprávám a ostatním se můžete zobrazit jako nedůvěryhodný."
"Without verifying, you wont have access to all your messages and may appear as untrusted to others.": "Bez ověření nebudete mít přístup ke všem svým zprávám a ostatním se můžete zobrazit jako nedůvěryhodný.",
"You are the only person here. If you leave, no one will be able to join in the future, including you.": "Jste zde jediná osoba. Pokud odejdete, nikdo se v budoucnu nebude moci připojit, včetně vás.",
"If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "Pokud vše resetujete, začnete bez důvěryhodných relací, bez důvěryhodných uživatelů a možná nebudete moci zobrazit minulé zprávy.",
"Only do this if you have no other device to complete verification with.": "Udělejte to, pouze pokud nemáte žádné jiné zařízení, se kterým byste mohli dokončit ověření.",
"Reset everything": "Resetovat vše",
"Forgotten or lost all recovery methods? <a>Reset all</a>": "Zapomněli nebo ztratili jste všechny metody obnovy? <a>Obnovit vše</a>",
"If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few moments whilst the index is recreated": "Pokud tak učiníte, nezapomeňte, že žádná z vašich zpráv nebude smazána, ale vyhledávání může být na několik okamžiků zpomaleno během opětovného vytvoření indexu",
"View message": "Zobrazit zprávu",
"Zoom in": "Přiblížit",
"Zoom out": "Oddálit",
"%(seconds)ss left": "Zbývá %(seconds)ss",
"Change server ACLs": "Změnit seznamy přístupů serveru",
"Show options to enable 'Do not disturb' mode": "Zobrazit možnosti pro povolení režimu „Nerušit“",
"You can select all or individual messages to retry or delete": "Můžete vybrat všechny nebo jednotlivé zprávy, které chcete zkusit poslat znovu nebo odstranit",
"Sending": "Odesílání",
"Retry all": "Zkusit všechny znovu",
"Delete all": "Smazat všechny",
"Some of your messages have not been sent": "Některé z vašich zpráv nebyly odeslány",
"%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s",
"%(count)s members including %(commaSeparatedMembers)s|other": "%(count)s členů včetně %(commaSeparatedMembers)s",
"Including %(commaSeparatedMembers)s": "Včetně %(commaSeparatedMembers)s",
"View all %(count)s members|one": "Zobrazit jednoho člena",
"View all %(count)s members|other": "Zobrazit všech %(count)s členů",
"Failed to send": "Odeslání se nezdařilo"
}

View file

@ -71,7 +71,7 @@
"No rooms to show": "Ingen rum at vise",
"This email address is already in use": "Denne email adresse er allerede i brug",
"This phone number is already in use": "Dette telefonnummer er allerede i brug",
"Failed to verify email address: make sure you clicked the link in the email": "Kunne ikke bekræfte emailaddressen: vær sikker på at klikke på linket i emailen",
"Failed to verify email address: make sure you clicked the link in the email": "Kunne ikke bekræfte emailaddressen: vær sikker på at klikke på linket i e-mailen",
"Call Timeout": "Opkalds Timeout",
"The remote side failed to pick up": "Den anden side tog den ikke",
"Unable to capture screen": "Kunne ikke optage skærm",
@ -473,9 +473,9 @@
"Show a placeholder for removed messages": "Vis en pladsholder for fjernede beskeder",
"Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Hvorvidt du benytter %(brand)s på en enhed, hvor touch er den primære input-grænseflade",
"Your user agent": "Din user agent",
"Use Single Sign On to continue": "Brug Single Sign On til at fortsætte",
"Use Single Sign On to continue": "Brug engangs login for at fortsætte",
"Confirm adding this email address by using Single Sign On to prove your identity.": "Bekræft tilføjelsen af denne email adresse ved at bruge Single Sign On til at bevise din identitet.",
"Single Sign On": "Single Sign On",
"Single Sign On": "Engangs login",
"Confirm adding email": "Bekræft tilføjelse af email",
"Click the button below to confirm adding this email address.": "Klik på knappen herunder for at bekræfte tilføjelsen af denne email adresse.",
"Confirm": "Bekræft",

View file

@ -47,7 +47,7 @@
"For security, this session has been signed out. Please sign in again.": "Aus Sicherheitsgründen wurde diese Sitzung beendet. Bitte melde dich erneut an.",
"Guests cannot join this room even if explicitly invited.": "Gäste können diesem Raum nicht beitreten, auch wenn sie explizit eingeladen wurden.",
"Hangup": "Auflegen",
"Homeserver is": "Der Heimserver ist",
"Homeserver is": "Dein Heimserver ist",
"Identity Server is": "Der Identitätsserver ist",
"I have verified my email address": "Ich habe meine E-Mail-Adresse verifiziert",
"Import E2E room keys": "E2E-Raumschlüssel importieren",
@ -78,7 +78,7 @@
"Someone": "Jemand",
"Success": "Erfolg",
"This doesn't appear to be a valid email address": "Dies scheint keine gültige E-Mail-Adresse zu sein",
"This room is not accessible by remote Matrix servers": "Ferngesteuerte Matrixserver können auf diesen Raum nicht zugreifen",
"This room is not accessible by remote Matrix servers": "Dieser Raum ist von Personen auf anderen Matrix-Servern nicht betretbar",
"Admin": "Administrator",
"Server may be unavailable, overloaded, or you hit a bug.": "Server ist nicht verfügbar, überlastet oder du bist auf einen Softwarefehler gestoßen.",
"Labs": "Labor",
@ -125,7 +125,7 @@
"Sat": "Sa",
"Jan": "Jan",
"Feb": "Feb",
"Mar": "Mrz",
"Mar": "Mär",
"Apr": "Apr",
"May": "Mai",
"Jun": "Jun",
@ -207,7 +207,7 @@
"Failed to kick": "Rauswurf fehlgeschlagen",
"Failed to mute user": "Stummschalten des Nutzers fehlgeschlagen",
"Failed to reject invite": "Ablehnen der Einladung ist fehlgeschlagen",
"Failed to set display name": "Anzeigename konnte nicht gesetzt werden",
"Failed to set display name": "Anzeigename konnte nicht geändert werden",
"Fill screen": "Fülle Bildschirm",
"Incorrect verification code": "Falscher Verifizierungscode",
"Join Room": "Raum beitreten",
@ -215,7 +215,7 @@
"not specified": "nicht angegeben",
"No more results": "Keine weiteren Ergebnisse",
"No results": "Keine Ergebnisse",
"OK": "OK",
"OK": "Ok",
"Search": "Suchen",
"Search failed": "Suche ist fehlgeschlagen",
"Server error": "Serverfehler",
@ -349,7 +349,7 @@
"If you already have a Matrix account you can <a>log in</a> instead.": "Wenn du bereits ein Matrix-Benutzerkonto hast, kannst du dich stattdessen auch direkt <a>anmelden</a>.",
"Home": "Startseite",
"Username invalid: %(errMessage)s": "Ungültiger Benutzername: %(errMessage)s",
"Accept": "Akzeptieren",
"Accept": "Annehmen",
"Active call (%(roomName)s)": "Aktiver Anruf (%(roomName)s)",
"Admin Tools": "Administratorwerkzeuge",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Verbindung zum Heimserver fehlgeschlagen - bitte überprüfe die Internetverbindung und stelle sicher, dass dem <a>SSL-Zertifikat deines Heimservers</a> vertraut wird und dass Anfragen nicht durch eine Browser-Erweiterung blockiert werden.",
@ -362,7 +362,7 @@
"Incoming video call from %(name)s": "Eingehender Videoanruf von %(name)s",
"Incoming voice call from %(name)s": "Eingehender Sprachanruf von %(name)s",
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Per <voiceText>Sprachanruf</voiceText> oder <videoText>Videoanruf</videoText> beitreten.",
"Last seen": "Zuletzt gesehen",
"Last seen": "Zuletzt gesehen um",
"No display name": "Kein Anzeigename",
"Private Chat": "Privater Chat",
"Public Chat": "Öffentlicher Chat",
@ -430,7 +430,7 @@
"Banned by %(displayName)s": "Verbannt von %(displayName)s",
"Description": "Beschreibung",
"Unable to accept invite": "Einladung kann nicht angenommen werden",
"Failed to invite users to %(groupId)s": "Benutzer konnten nicht in %(groupId)s eingeladen werden",
"Failed to invite users to %(groupId)s": "Einige Leute konnten nicht in %(groupId)s eingeladen werden",
"Unable to reject invite": "Einladung konnte nicht abgelehnt werden",
"Who would you like to add to this summary?": "Wen möchtest zu dieser Übersicht hinzufügen?",
"Add to summary": "Zur Übersicht hinzufügen",
@ -469,7 +469,7 @@
"Which rooms would you like to add to this community?": "Welche Räume sollen zu dieser Community hinzugefügt werden?",
"Add rooms to the community": "Räume zur Community hinzufügen",
"Add to community": "Zur Community hinzufügen",
"Failed to invite users to community": "Benutzer konnten nicht in die Community eingeladen werden",
"Failed to invite users to community": "Einige Leute konnten nicht in die Community eingeladen werden",
"Communities": "Communities",
"Invalid community ID": "Ungültige Community-ID",
"'%(groupId)s' is not a valid community ID": "'%(groupId)s' ist keine gültige Community-ID",
@ -504,7 +504,7 @@
"Delete Widget": "Widget löschen",
"Mention": "Erwähnen",
"Invite": "Einladen",
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Das Löschen eines Widgets entfernt es für alle Nutzer in diesem Raum. Möchtest du es wirklich löschen?",
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Das Löschen des Widgets entfernt es für alle in diesem Raum. Wirklich löschen?",
"Mirror local video feed": "Lokalen Video-Feed spiegeln",
"Failed to withdraw invitation": "Die Einladung konnte nicht zurückgezogen werden",
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community-IDs dürfen nur die folgenden Zeichen enthalten: a-z, 0-9, or '=_-./'",
@ -582,13 +582,13 @@
"Notify the whole room": "Alle im Raum benachrichtigen",
"Room Notification": "Raum-Benachrichtigung",
"These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Diese Räume werden Community-Mitgliedern auf der Community-Seite angezeigt. Community-Mitglieder können diesen Räumen beitreten, indem sie diese anklicken.",
"Show these rooms to non-members on the community page and room list?": "Sollen diese Räume öffentlich auf der Community-Seite und in der Raum-Liste angezeigt werden?",
"Show these rooms to non-members on the community page and room list?": "Sollen diese Räume öffentlich auf der Communityseite und in der Raumliste angezeigt werden?",
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML für deine Community-Seite</h1>\n<p>\n Nutze die ausführliche Beschreibung, um neuen Mitgliedern diese Community vorzustellen\n oder um wichtige <a href=\"foo\">Links</a> bereitzustellen.\n</p>\n<p>\n Du kannst sogar 'img'-Tags (HTML) verwenden\n</p>\n",
"Your community hasn't got a Long Description, a HTML page to show to community members.<br />Click here to open settings and give it one!": "Deine Community hat noch keine ausführliche Beschreibung, d. h. eine HTML-Seite, die Community-Mitgliedern angezeigt wird.<br />Hier klicken, um die Einstellungen zu öffnen und eine Beschreibung zu erstellen!",
"Enable inline URL previews by default": "URL-Vorschau standardmäßig aktivieren",
"Enable URL previews for this room (only affects you)": "URL-Vorschau für dich in diesem Raum",
"Enable URL previews by default for participants in this room": "URL-Vorschau für Raummitglieder",
"Please note you are logging into the %(hs)s server, not matrix.org.": "Du meldest dich gerade am %(hs)s-Server an, nicht auf matrix.org.",
"Please note you are logging into the %(hs)s server, not matrix.org.": "Du meldest dich gerade am Server von %(hs)s an, nicht auf matrix.org.",
"There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "Sonst ist hier aktuell niemand. Möchtest du <inviteText>Benutzer einladen</inviteText> oder die <nowarnText>Warnmeldung bezüglich des leeren Raums deaktivieren</nowarnText>?",
"URL previews are disabled by default for participants in this room.": "URL-Vorschau ist für Mitglieder des Raumes standardmäßig deaktiviert.",
"URL previews are enabled by default for participants in this room.": "URL-Vorschau ist für Mitglieder des Raumes standardmäßig aktiviert.",
@ -647,14 +647,14 @@
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Wenn du einen Fehler via GitHub meldest, können Fehlerberichte uns helfen um das Problem zu finden. Sie enthalten Anwendungsdaten wie deinen Nutzernamen, Raum- und Gruppen-IDs und Aliase, die du besucht hast sowie Nutzernamen anderer Nutzer mit denen du schreibst. Sie enthalten keine Nachrichten.",
"Submit debug logs": "Fehlerberichte einreichen",
"Code": "Code",
"Opens the Developer Tools dialog": "Öffnet die Entwicklerwerkzeuge",
"Opens the Developer Tools dialog": "Entwickler-Werkzeuge öffnen",
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Von %(displayName)s (%(userName)s) um %(dateTime)s gesehen",
"Unable to join community": "Community konnte nicht betreten werden",
"Unable to leave community": "Community konnte nicht verlassen werden",
"Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.": "Änderungen am <bold1>Namen</bold1> und <bold2>Bild</bold2> deiner Community werden evtl. erst nach 30 Minuten von anderen Nutzern gesehen werden.",
"Join this community": "Community beitreten",
"Leave this community": "Community verlassen",
"You don't currently have any stickerpacks enabled": "Du hast aktuell keine Stickerpakete aktiviert",
"You don't currently have any stickerpacks enabled": "Keine Stickerpakete aktiviert",
"Hide Stickers": "Sticker ausblenden",
"Show Stickers": "Sticker anzeigen",
"Who can join this community?": "Wer kann dieser Community beitreten?",
@ -708,7 +708,7 @@
"Messages containing <span>keywords</span>": "Nachrichten mit <span>Schlüsselwörtern</span>",
"Error saving email notification preferences": "Fehler beim Speichern der E-Mail-Benachrichtigungseinstellungen",
"Tuesday": "Dienstag",
"Enter keywords separated by a comma:": "Schlüsselwörter kommagetrennt eingeben:",
"Enter keywords separated by a comma:": "Gib die Schlüsselwörter durch einen Beistrich getrennt ein:",
"Forward Message": "Nachricht weiterleiten",
"You have successfully set a password and an email address!": "Du hast erfolgreich ein Passwort und eine E-Mail-Adresse gesetzt!",
"Remove %(name)s from the directory?": "Soll der Raum %(name)s aus dem Verzeichnis entfernt werden?",
@ -781,7 +781,7 @@
"Thank you!": "Danke!",
"Uploaded on %(date)s by %(user)s": "Hochgeladen: %(date)s von %(user)s",
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "In deinem aktuell verwendeten Browser können Aussehen und Handhabung der Anwendung unter Umständen noch komplett fehlerhaft sein, so dass einige bzw. im Extremfall alle Funktionen nicht zur Verfügung stehen. Du kannst es trotzdem versuchen und fortfahren, bist dabei aber bezüglich aller auftretenden Probleme auf dich allein gestellt!",
"Checking for an update...": "Nach Aktualisierung suchen...",
"Checking for an update...": "Nach Aktualisierungen suchen...",
"Missing roomId.": "Fehlende Raum-ID.",
"Every page you use in the app": "Jede Seite, die du in der App benutzt",
"e.g. <CurrentPageURL>": "z. B. <CurrentPageURL>",
@ -903,7 +903,7 @@
"Avoid repeated words and characters": "Vermeide wiederholte Worte und Zeichen",
"Avoid sequences": "Vermeide Sätze",
"Avoid recent years": "Vermeide die letzten Jahre",
"Avoid years that are associated with you": "Vermeide Jahre, die mit dir zusammenhängen",
"Avoid years that are associated with you": "Vermeide Jahreszahlen, die mit dir zu tun haben",
"Avoid dates and years that are associated with you": "Vermeide Daten und Jahre, die mit dir in Verbindung stehen",
"Capitalization doesn't help very much": "Großschreibung hilft nicht viel",
"All-uppercase is almost as easy to guess as all-lowercase": "Alles groß zu schreiben ist genauso einfach zu erraten, wie alles klein zu schreiben",
@ -978,7 +978,7 @@
"%(names)s and %(lastPerson)s are typing …": "%(names)s und %(lastPerson)s tippen…",
"Render simple counters in room header": "Einfache Zähler in Raumkopfzeile anzeigen",
"Enable Emoji suggestions while typing": "Emojivorschläge während Eingabe",
"Show a placeholder for removed messages": "Zeigt einen Platzhalter für gelöschte Nachrichten an",
"Show a placeholder for removed messages": "Platzhalter für gelöschte Nachrichten",
"Show join/leave messages (invites/kicks/bans unaffected)": "Betreten oder Verlassen von Benutzern (ausgen. Einladungen/Rauswürfe/Banne)",
"Show avatar changes": "Avataränderungen anzeigen",
"Show display name changes": "Änderungen von Anzeigenamen",
@ -1036,7 +1036,7 @@
"%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s erlaubte Gäste diesem Raum beizutreten.",
"%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s hat Gästen verboten, diesem Raum beizutreten.",
"%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s änderte den Gastzugriff auf '%(rule)s'",
"Group & filter rooms by custom tags (refresh to apply changes)": "Gruppiere und filtere Räume nach eigenen Tags (neu laden um Änderungen zu übernehmen)",
"Group & filter rooms by custom tags (refresh to apply changes)": "[Veraltet] Gruppiere und filtere Räume nach eigenen Tags (neu laden um Änderungen zu übernehmen)",
"Unable to find a supported verification method.": "Konnte keine unterstützte Verifikationsmethode finden.",
"Dog": "Hund",
"Cat": "Katze",
@ -1101,12 +1101,12 @@
"Folder": "Ordner",
"Pin": "Anheften",
"Timeline": "Chatverlauf",
"Autocomplete delay (ms)": "Verzögerung zur Autovervollständigung (ms)",
"Autocomplete delay (ms)": "Verzögerung vor Autovervollständigung (ms)",
"Roles & Permissions": "Rollen und Berechtigungen",
"Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Änderungen an der Sichtbarkeit des Chatverlaufs gelten nur für zukünftige Nachrichten. Die Sichtbarkeit des existierenden Verlaufs bleibt unverändert.",
"Security & Privacy": "Sicherheit",
"Encryption": "Verschlüsselung",
"Once enabled, encryption cannot be disabled.": "Sobald aktiviert, kann die Verschlüsselung nicht mehr deaktiviert werden.",
"Once enabled, encryption cannot be disabled.": "Sobald du die Verschlüsselung aktivierst, kann du sie nicht mehr deaktivieren.",
"Encrypted": "Verschlüsselt",
"Ignored users": "Blockierte Benutzer",
"Key backup": "Schlüsselsicherung",
@ -1190,7 +1190,7 @@
"Bulk options": "Sammeloptionen",
"Join millions for free on the largest public server": "Schließe dich kostenlos auf dem größten öffentlichen Server Millionen von Menschen an",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Stellt ¯\\_(ツ)_/¯ einer Klartextnachricht voran",
"Changes your display nickname in the current room only": "Ändert den Anzeigenamen ausschließlich für den aktuellen Raum",
"Changes your display nickname in the current room only": "Ändert deinen Anzeigenamen nur für den aktuellen Raum",
"%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s hat Abzeichen der Gruppen %(groups)s für diesen Raum aktiviert.",
"%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s hat Abzeichen der Gruppen %(groups)s in diesem Raum deaktiviert.",
"%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s hat Abzeichen von %(newGroups)s aktiviert und von %(oldGroups)s deaktiviert.",
@ -1203,9 +1203,9 @@
"Change room avatar": "Ändere Raumbild",
"Change room name": "Ändere Raumname",
"Change main address for the room": "Ändere Hauptadresse für den Raum",
"Change history visibility": "Ändere Sichtbarkeit der Historie",
"Change history visibility": "Sichtbarkeit des Verlaufs ändern",
"Change permissions": "Ändere Berechtigungen",
"Change topic": "Ändere das Thema",
"Change topic": "Thema ändern",
"Modify widgets": "Widgets bearbeiten",
"Default role": "Standard-Rolle",
"Send messages": "Nachrichten senden",
@ -1249,7 +1249,7 @@
"Please supply a https:// or http:// widget URL": "Bitte gib eine mit https:// oder http:// beginnende Widget-URL an",
"Sends the given emote coloured as a rainbow": "Zeigt Aktionen in Regenbogenfarben",
"%(senderName)s made no change.": "%(senderName)s hat keine Änderung vorgenommen.",
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s hat die Einladung zum Raumbeitritt für %(targetDisplayName)s zurückgezogen.",
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s hat die Einladung für %(targetDisplayName)s zurückgezogen.",
"Cannot reach homeserver": "Der Heimserver ist nicht erreichbar",
"Ensure you have a stable internet connection, or get in touch with the server admin": "Stelle sicher, dass du eine stabile Internetverbindung hast oder wende dich an deinen Serveradministrator",
"Ask your %(brand)s admin to check <a>your config</a> for incorrect or duplicate entries.": "Wende dich an deinen %(brand)s-Admin um <a>deine Konfiguration</a> auf ungültige oder doppelte Einträge zu überprüfen.",
@ -1446,7 +1446,7 @@
"Go": "Los",
"Command Help": "Befehl Hilfe",
"To help us prevent this in future, please <a>send us logs</a>.": "Um uns zu helfen, dies in Zukunft zu vermeiden, <a>sende uns bitte die Protokolldateien</a>.",
"Notification settings": "Benachrichtigungseinstellungen",
"Notification settings": "Benachrichtigungen",
"Help": "Hilf uns",
"Filter": "Filtern",
"Filter rooms…": "Räume filtern…",
@ -1459,7 +1459,7 @@
"Your key share request has been sent - please check your other sessions for key share requests.": "Deine Schlüsselanfrage wurde gesendet - sieh in deinen anderen Sitzungen nach der Schlüsselanfrage.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Schlüsselanfragen werden automatisch an deine anderen Sitzungen gesendet. Wenn du sie abgelehnt oder ignoriert hast, klicke hier, um die Schlüssel erneut anzufordern.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Wenn deine anderen Sitzungen nicht über den Schlüssel für diese Nachricht verfügen, kannst du die Nachricht nicht entschlüsseln.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Fordere die Schlüssel aus deinen anderen Sitzungen erneut an</requestLink>.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Schlüssel aus deinen anderen Sitzungen erneut anfordern</requestLink>.",
"Room %(name)s": "Raum %(name)s",
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Ein Aktualisierung dieses Raums deaktiviert die aktuelle Instanz des Raums und erstellt einen aktualisierten Raum mit demselben Namen.",
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) hat sich zu einer neuen Sitzung angemeldet, ohne sie zu verifizieren:",
@ -2028,7 +2028,7 @@
"Doesn't look like a valid email address": "Das sieht nicht nach einer gültigen E-Mail-Adresse aus",
"Enter phone number (required on this homeserver)": "Telefonnummer eingeben (auf diesem Heimserver erforderlich)",
"Doesn't look like a valid phone number": "Das sieht nicht nach einer gültigen Telefonnummer aus",
"Sign in with SSO": "Mit Single-Sign-On anmelden",
"Sign in with SSO": "Einmalanmeldung verwenden",
"Welcome to %(appName)s": "Willkommen bei %(appName)s",
"Send a Direct Message": "Direktnachricht senden",
"Create a Group Chat": "Gruppenchat erstellen",
@ -2107,7 +2107,7 @@
"Failed to re-authenticate due to a homeserver problem": "Erneute Authentifizierung aufgrund eines Problems im Heimserver fehlgeschlagen",
"Failed to re-authenticate": "Erneute Authentifizierung fehlgeschlagen",
"Command Autocomplete": "Autovervollständigung aktivieren",
"Community Autocomplete": "Community-Auto-Vervollständigung",
"Community Autocomplete": "Community-Autovervollständigung",
"DuckDuckGo Results": "DuckDuckGo Ergebnisse",
"Great! This recovery passphrase looks strong enough.": "Super! Diese Wiederherstellungspassphrase sieht stark genug aus.",
"Enter a recovery passphrase": "Gib eine Wiederherstellungspassphrase ein",
@ -2129,12 +2129,12 @@
"Navigation": "Navigation",
"Calls": "Anrufe",
"Room List": "Raumliste",
"Autocomplete": "Auto-Vervollständigung",
"Autocomplete": "Autovervollständigung",
"Alt": "Alt",
"Toggle microphone mute": "Schalte Mikrofon stumm/an",
"Toggle video on/off": "Schalte Video an/aus",
"Toggle microphone mute": "Mikrofon an-/ausschalten",
"Toggle video on/off": "Video an-/ausschalten",
"Jump to room search": "Zur Raumsuche springen",
"Close dialog or context menu": "Schließe Dialog oder Kontextmenü",
"Close dialog or context menu": "Dialog oder Kontextmenü schließen",
"Cancel autocomplete": "Autovervollständigung deaktivieren",
"Unable to revoke sharing for email address": "Dem Teilen der E-Mail-Adresse kann nicht widerrufen werden",
"Unable to validate homeserver/identity server": "Heimserver/Identitätsserver nicht validierbar",
@ -2172,13 +2172,13 @@
"Activate selected button": "Ausgewählten Button aktivieren",
"Toggle right panel": "Rechtes Panel ein-/ausblenden",
"Toggle this dialog": "Diesen Dialog ein-/ausblenden",
"Move autocomplete selection up/down": "Auto-Vervollständigung nach oben/unten verschieben",
"Move autocomplete selection up/down": "Autovervollständigung nach oben/unten verschieben",
"Opens chat with the given user": "Öffnet einen Chat mit diesem Benutzer",
"Sends a message to the given user": "Sendet diesem Benutzer eine Nachricht",
"Waiting for your other session to verify…": "Warte auf die Verifikation deiner anderen Sitzungen…",
"You've successfully verified your device!": "Du hast dein Gerät erfolgreich verifiziert!",
"QR Code": "QR-Code",
"To continue, use Single Sign On to prove your identity.": "Zum Fortfahren, nutze Single Sign-On um deine Identität zu bestätigen.",
"To continue, use Single Sign On to prove your identity.": "Zum Fortfahren nutze die Einmalanmeldung um deine Identität zu bestätigen.",
"Confirm to continue": "Bestätige um fortzufahren",
"Click the button below to confirm your identity.": "Klicke den Button unten um deine Identität zu bestätigen.",
"Confirm encryption setup": "Bestätige die Einrichtung der Verschlüsselung",
@ -2257,8 +2257,8 @@
"Light": "Hell",
"Dark": "Dunkel",
"Use the improved room list (will refresh to apply changes)": "Verwende die verbesserte Raumliste (lädt die Anwendung neu)",
"Use custom size": "Verwende individuelle Größe",
"Hey you. You're the best!": "Hey du. Du bist großartig.",
"Use custom size": "Andere Schriftgröße verwenden",
"Hey you. You're the best!": "Hey du. Du bist großartig!",
"Message layout": "Nachrichtenlayout",
"Compact": "Kompakt",
"Modern": "Modern",
@ -2421,7 +2421,7 @@
"Download logs": "Protokolle herunterladen",
"Unexpected server error trying to leave the room": "Unerwarteter Serverfehler beim Versuch den Raum zu verlassen",
"Error leaving room": "Fehler beim Verlassen des Raums",
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 Prototyp. Benötigt einen kompatiblen Heimserver. Höchst experimentell - mit Vorsicht verwenden.",
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "[Veraltet] Communities v2 Prototyp. Benötigt einen kompatiblen Heimserver. Höchst experimentell - mit Vorsicht verwenden.",
"Explore rooms in %(communityName)s": "Räume in %(communityName)s erkunden",
"Set up Secure Backup": "Schlüsselsicherung einrichten",
"Information": "Information",
@ -2570,7 +2570,7 @@
"Change the avatar of this room": "Icon von diesem Raum ändern",
"See when the avatar changes in this room": "Sehen, wenn sich das Icon des Raums ändert",
"Change the avatar of your active room": "Den Avatar deines aktiven Raums ändern",
"See when the avatar changes in your active room": "Sehen wenn ein Avatar in deinem aktiven Raum geändert wird",
"See when the avatar changes in your active room": "Sehen, wenn das Icon in deinem aktiven Raum geändert wird",
"Send stickers to this room as you": "Einen Sticker in diesen Raum senden",
"See when a sticker is posted in this room": "Sehe wenn ein Sticker in diesen Raum gesendet wird",
"Send stickers to your active room as you": "Einen Sticker als du in deinen aktiven Raum senden",
@ -2597,7 +2597,7 @@
"See videos posted to your active room": "In deinen aktiven Raum gesendete Videos anzeigen",
"See videos posted to this room": "In diesen Raum gesendete Videos anzeigen",
"Send images as you in this room": "Bilder als du in diesen Raum senden",
"Send images as you in your active room": "Bilder als du in deinem aktiven Raum senden",
"Send images as you in your active room": "Sende Bilder als deine Person in den aktiven Raum.",
"See images posted to this room": "In diesen Raum gesendete Bilder anzeigen",
"See images posted to your active room": "In deinen aktiven Raum gesendete Bilder anzeigen",
"Send videos as you in this room": "Videos als du in diesen Raum senden",
@ -2621,7 +2621,7 @@
"Call Paused": "Anruf pausiert",
"Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|other": "Verschlüsselte Nachrichten sicher lokal zwischenspeichern, um sie in Suchergebnissen finden zu können. Es werden %(size)s benötigt, um die Nachrichten von %(rooms)s Räumen zu speichern.",
"Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|one": "Verschlüsselte Nachrichten sicher lokal zwischenspeichern, um sie in Suchergebnissen finden zu können. Es werden %(size)s benötigt, um die Nachrichten vom Raum %(rooms)s zu speichern.",
"Only the two of you are in this conversation, unless either of you invites anyone to join.": "Nur ihr zwei seid in dieser Konversation, außer ihr ladet jemanden Neues ein.",
"Only the two of you are in this conversation, unless either of you invites anyone to join.": "Nur ihr beide nehmt an dieser Konversation teil, es sei denn, ihr ladet jemanden ein.",
"This is the beginning of your direct message history with <displayName/>.": "Dies ist der Beginn deiner Direktnachrichten mit <displayName/>.",
"Topic: %(topic)s (<a>edit</a>)": "Thema: %(topic)s (<a>ändern</a>)",
"Topic: %(topic)s ": "Thema: %(topic)s ",
@ -2945,7 +2945,7 @@
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.": "Du kannst in den benutzerdefinierten Serveroptionen eine andere Heimserver-URL angeben, um dich bei anderen Matrixservern anzumelden.",
"Server Options": "Servereinstellungen",
"No other application is using the webcam": "keine andere Anwendung auf die Webcam zugreift",
"Permission is granted to use the webcam": "auf die Webcam zugegriffen werden darf",
"Permission is granted to use the webcam": "Zugriff auf die Webcam ist gestattet.",
"A microphone and webcam are plugged in and set up correctly": "Mikrofon und Webcam eingesteckt und richtig eingerichtet sind",
"Call failed because no microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Der Anruf ist fehlgeschlagen weil nicht auf das Mikrofon zugegriffen werden konnte. Stelle sicher, dass das Mikrofon richtig eingesteckt und eingerichtet ist.",
"Call failed because no webcam or microphone could not be accessed. Check that:": "Der Anruf ist fehlgeschlagen weil nicht auf das Mikrofon oder die Webcam zugegriffen werden konnte. Stelle sicher, dass:",
@ -2954,7 +2954,7 @@
"Host account on": "Benutzer*innenkonto betreiben an",
"Hold": "Halten",
"Resume": "Fortsetzen",
"We call the places where you can host your account homeservers.": "Den Ort, an dem du dein Benutzer*innenkonto betreibst, nennen wir „Heimserver“.",
"We call the places where you can host your account homeservers.": "Den Ort, an dem du dein Konto betreibst, nennen wir „Heimserver“.",
"Invalid URL": "Ungültiger Link",
"Unable to validate homeserver": "Überprüfung des Heimservers nicht möglich",
"%(name)s paused": "%(name)s hat pausiert",
@ -3003,7 +3003,7 @@
"Workspace: <networkLink/>": "Arbeitsraum: <networkLink/>",
"Dial pad": "Wähltastatur",
"There was an error looking up the phone number": "Beim Suchen der Telefonnummer ist ein Fehler aufgetreten",
"Change which room, message, or user you're viewing": "Ändere welchen Raum, Nachricht oder Nutzer du siehst",
"Change which room, message, or user you're viewing": "Ändere den sichtbaren Raum, Nachricht oder Nutzer",
"Unable to look up phone number": "Telefonnummer konnte nicht gefunden werden",
"This session has detected that your Security Phrase and key for Secure Messages have been removed.": "In dieser Sitzung wurde festgestellt, dass deine Sicherheitsphrase und dein Schlüssel für sichere Nachrichten entfernt wurden.",
"A new Security Phrase and key for Secure Messages have been detected.": "Eine neue Sicherheitsphrase und ein neuer Schlüssel für sichere Nachrichten wurden erkannt.",
@ -3025,7 +3025,7 @@
"Security Key mismatch": "Nicht übereinstimmende Sicherheitsschlüssel",
"Set my room layout for everyone": "Diese Raumgestaltung für alle setzen",
"%(senderName)s has updated the widget layout": "%(senderName)s hat das Widget-Layout aktualisiert",
"Search (must be enabled)": "Suche (muss aktiviert sein)",
"Search (must be enabled)": "Suchen (muss in den Einstellungen aktiviert sein)",
"Remember this": "Dies merken",
"The widget will verify your user ID, but won't be able to perform actions for you:": "Das Widget überprüft deine Nutzer-ID, kann jedoch keine Aktionen für dich ausführen:",
"Allow this widget to verify your identity": "Erlaube diesem Widget deine Identität zu überprüfen",
@ -3085,7 +3085,7 @@
"Save changes": "Änderungen speichern",
"Undo": "Rückgängig",
"Save Changes": "Änderungen Speichern",
"View dev tools": "Entwicklerwerkzeuge anzeigen",
"View dev tools": "Entwicklerwerkzeuge",
"Apply": "Anwenden",
"Create a new room": "Neuen Raum erstellen",
"Suggested Rooms": "Vorgeschlagene Räume",
@ -3094,7 +3094,7 @@
"New room": "Neuer Raum",
"Share invite link": "Einladungslink teilen",
"Click to copy": "Klicken um zu kopieren",
"Collapse space panel": "Space-Feld zuklappen",
"Collapse space panel": "Space-Feld einklappen",
"Expand space panel": "Space-Feld aufklappen",
"Creating...": "Erstelle...",
"You can change these at any point.": "Du kannst diese jederzeit ändern.",
@ -3161,7 +3161,7 @@
"Invite to %(spaceName)s": "Leute zu %(spaceName)s einladen",
"Spaces": "Spaces",
"Invite People": "Personen einladen",
"Invite with email or username": "Personen mit E-Mail oder Benutzername einladen",
"Invite with email or username": "Personen mit E-Mail oder Benutzernamen einladen",
"You can change these anytime.": "Du kannst diese jederzeit ändern.",
"From %(deviceName)s (%(deviceId)s) at %(ip)s": "Von %(deviceName)s (%(deviceId)s) mit der Adresse %(ip)s",
"A new login is accessing your account: %(name)s (%(deviceID)s) at %(ip)s": "Neue Anmeldung: %(name)s (%(deviceID)s) mit der IP-Adresse %(ip)s",
@ -3222,7 +3222,7 @@
"Manage & explore rooms": "Räume entdecken und verwalten",
"unknown person": "unbekannte Person",
"Send and receive voice messages (in development)": "Sprachnachrichten senden und empfangen (in der Entwicklung)",
"Check your devices": "Überprüfe dein Gerät",
"Check your devices": "Überprüfe deine Sitzungen",
"%(deviceId)s from %(ip)s": "%(deviceId)s von %(ip)s",
"This homeserver has been blocked by it's administrator.": "Dieser Heimserver wurde von seiner Administration blockiert.",
"You have unverified logins": "Du hast nicht-bestätigte Anmeldungen",
@ -3243,8 +3243,44 @@
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Soll die Host-Erstellung wirklich abgebrochen werden? Dieser Prozess kann nicht wieder fortgesetzt werden.",
"Invite to just this room": "Nur für diesen Raum einladen",
"Consult first": "Konsultiere zuerst",
"Reset event store?": "Ereigniss-Speicher zurück setzen?",
"You most likely do not want to reset your event index store": "Es ist wahrscheinlich, dass du den Ereigniss-Index-Speicher nicht zurück setzen möchtest",
"Reset event store?": "Ereignisspeicher zurück setzen?",
"You most likely do not want to reset your event index store": "Es ist wahrscheinlich, dass du den Ereignis-Indexspeicher nicht zurück setzen möchtest",
"If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few momentswhilst the index is recreated": "Falls du dies tust, werden keine deiner Nachrichten gelöscht. Allerdings wird die Such-Funktion eine Weile lang schlecht funktionieren, bis der Index wieder hergestellt ist",
"Reset event store": "Ereignis-Speicher zurück setzen"
"Reset event store": "Ereignisspeicher zurück setzen",
"Show options to enable 'Do not disturb' mode": "Optionen für den \"Nicht-Stören-Modus\" anzeigen",
"You can add more later too, including already existing ones.": "Natürlich kannst du jederzeit weitere Räume hinzufügen.",
"Let's create a room for each of them.": "Wir erstellen dir für jedes Thema einen Raum.",
"What are some things you want to discuss in %(spaceName)s?": "Welche Themen willst du in %(spaceName)s besprechen?",
"Inviting...": "Einladen...",
"Failed to create initial space rooms": "Fehler beim Initialisieren des Space",
"You are the only person here. If you leave, no one will be able to join in the future, including you.": "Du bist hier noch alleine. Wenn du den Space verlässt, ist er für immer verloren (eine lange Zeit).",
"Edit settings relating to your space.": "Einstellungen vom Space bearbeiten.",
"Please choose a strong password": "Bitte gib ein sicheres Passwort ein",
"If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "Wenn du alles zurücksetzt, gehen alle verifizierten Anmeldungen, Benutzer und verschlüsselte Nachrichten verloren.",
"Only do this if you have no other device to complete verification with.": "Verwende es nur, wenn du kein Gerät, mit dem du dich verifizieren, kannst bei dir hast.",
"Reset everything": "Alles zurücksetzen",
"Forgotten or lost all recovery methods? <a>Reset all</a>": "Hast du alle Wiederherstellungsmethoden vergessen? <a>Setze sie hier zurück</a>",
"View message": "Nachricht anzeigen",
"Zoom in": "Vergrößern",
"Zoom out": "Verkleinern",
"%(seconds)ss left": "%(seconds)s vergangen",
"Change server ACLs": "ACLs des Servers bearbeiten",
"Failed to send": "Fehler beim Senden",
"View all %(count)s members|other": "Alle %(count)s Mitglieder anzeigen",
"View all %(count)s members|one": "Mitglied anzeigen",
"%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s",
"Some of your messages have not been sent": "Einige Nachrichten konnten nicht gesendet werden",
"Original event source": "Ursprüngliche Rohdaten",
"Decrypted event source": "Entschlüsselte Rohdaten",
"Sending": "Senden",
"You can select all or individual messages to retry or delete": "Du kannst einzelne oder alle Nachrichten erneut senden oder löschen",
"Delete all": "Alle löschen",
"Retry all": "Alle erneut senden",
"Without verifying, you wont have access to all your messages and may appear as untrusted to others.": "Ohne deine Verifikation kannst du keine deiner verschlüsselten Nachrichten lesen und andere vertrauen dir möglicherweise nicht.",
"Verify your identity to access encrypted messages and prove your identity to others.": "Verifiziere diese Anmeldung, um auf verschlüsselte Nachrichten zuzugreifen und dich anderen gegenüber zu identifizieren.",
"Use another login": "Mit anderem Gerät verifizeren",
"If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few moments whilst the index is recreated": "Falls du es wirklich willst: Es werden keine Nachrichten gelöscht. Außerdem wird die Suche, während der Index erstellt wird, etwas langsamer sein",
"%(count)s members including %(commaSeparatedMembers)s|other": "%(count)s Mitglieder inklusive %(commaSeparatedMembers)s",
"Including %(commaSeparatedMembers)s": "Inklusive%(commaSeparatedMembers)s",
"Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>": "Beratung mit %(transferTarget)s. <a>Übertragung zu %(transferee)s</a>"
}

View file

@ -840,7 +840,7 @@
"Match system theme": "Match system theme",
"Use a system font": "Use a system font",
"System font name": "System font name",
"Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls",
"Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)",
"Send analytics data": "Send analytics data",
"Never send encrypted messages to unverified sessions from this session": "Never send encrypted messages to unverified sessions from this session",
"Never send encrypted messages to unverified sessions in this room from this session": "Never send encrypted messages to unverified sessions in this room from this session",
@ -1259,8 +1259,9 @@
"olm version:": "olm version:",
"Homeserver is": "Homeserver is",
"Identity Server is": "Identity Server is",
"Access Token:": "Access Token:",
"click to reveal": "click to reveal",
"Access Token": "Access Token",
"Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.",
"Copy": "Copy",
"Clear cache and reload": "Clear cache and reload",
"Labs": "Labs",
"Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. <a>Learn more</a>.": "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. <a>Learn more</a>.",
@ -1559,6 +1560,8 @@
"Explore all public rooms": "Explore all public rooms",
"Quick actions": "Quick actions",
"Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below",
"%(count)s results in all spaces|other": "%(count)s results in all spaces",
"%(count)s results in all spaces|one": "%(count)s result in all spaces",
"%(count)s results|other": "%(count)s results",
"%(count)s results|one": "%(count)s result",
"This room": "This room",
@ -1654,6 +1657,10 @@
"Invited by %(sender)s": "Invited by %(sender)s",
"Jump to first unread message.": "Jump to first unread message.",
"Mark all as read": "Mark all as read",
"Unable to access your microphone": "Unable to access your microphone",
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
"No microphone found": "No microphone found",
"We didn't find a microphone on your device. Please check your settings and try again.": "We didn't find a microphone on your device. Please check your settings and try again.",
"Record a voice message": "Record a voice message",
"Stop the recording": "Stop the recording",
"Delete recording": "Delete recording",
@ -1853,6 +1860,7 @@
"%(name)s wants to verify": "%(name)s wants to verify",
"You sent a verification request": "You sent a verification request",
"Error decrypting video": "Error decrypting video",
"Error processing voice message": "Error processing voice message",
"Show all": "Show all",
"Reactions": "Reactions",
"<reactors/><reactedWith> reacted with %(content)s</reactedWith>": "<reactors/><reactedWith> reacted with %(content)s</reactedWith>",
@ -2032,10 +2040,11 @@
"Direct Messages": "Direct Messages",
"Space selection": "Space selection",
"Add existing rooms": "Add existing rooms",
"Don't want to add an existing room?": "Don't want to add an existing room?",
"Not all selected were added": "Not all selected were added",
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
"Want to add a new room instead?": "Want to add a new room instead?",
"Create a new room": "Create a new room",
"Failed to add rooms to space": "Failed to add rooms to space",
"Adding...": "Adding...",
"Matrix ID": "Matrix ID",
"Matrix Room ID": "Matrix Room ID",
"email address": "email address",
@ -2346,7 +2355,6 @@
"Share Community": "Share Community",
"Share Room Message": "Share Room Message",
"Link to selected message": "Link to selected message",
"Copy": "Copy",
"Command Help": "Command Help",
"Failed to save space settings.": "Failed to save space settings.",
"Space settings": "Space settings",
@ -2624,6 +2632,7 @@
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.",
"Explore rooms in %(communityName)s": "Explore rooms in %(communityName)s",
"Filter": "Filter",
"Filter all spaces": "Filter all spaces",
"Clear filter": "Clear filter",
"Filter rooms and people": "Filter rooms and people",
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.",
@ -2681,6 +2690,8 @@
"Failed to create initial space rooms": "Failed to create initial space rooms",
"Skip for now": "Skip for now",
"Creating rooms...": "Creating rooms...",
"Failed to add rooms to space": "Failed to add rooms to space",
"Adding...": "Adding...",
"What do you want to organise?": "What do you want to organise?",
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.",
"Share %(name)s": "Share %(name)s",
@ -2820,7 +2831,7 @@
"Use a different passphrase?": "Use a different passphrase?",
"That doesn't match.": "That doesn't match.",
"Go back to set it again.": "Go back to set it again.",
"Please enter your Security Phrase a second time to confirm.": "Please enter your Security Phrase a second time to confirm.",
"Enter your Security Phrase a second time to confirm it.": "Enter your Security Phrase a second time to confirm it.",
"Repeat your Security Phrase...": "Repeat your Security Phrase...",
"Your Security Key is a safety net - you can use it to restore access to your encrypted messages if you forget your Security Phrase.": "Your Security Key is a safety net - you can use it to restore access to your encrypted messages if you forget your Security Phrase.",
"Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.",
@ -2850,8 +2861,6 @@
"You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.",
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
"Enter a security phrase only you know, as its used to safeguard your data. To be secure, you shouldnt re-use your account password.": "Enter a security phrase only you know, as its used to safeguard your data. To be secure, you shouldnt re-use your account password.",
"Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.",
"Confirm your recovery passphrase": "Confirm your recovery passphrase",
"Store your Security Key somewhere safe, like a password manager or a safe, as its used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as its used to safeguard your encrypted data.",
"Unable to query secret storage status": "Unable to query secret storage status",
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",

Some files were not shown because too many files have changed in this diff Show more