Apply prettier formatting

This commit is contained in:
Michael Weimann 2022-12-12 12:24:14 +01:00
parent 1cac306093
commit 526645c791
No known key found for this signature in database
GPG key ID: 53F535A266BB9584
1576 changed files with 65385 additions and 62478 deletions

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, { useState } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
import AccessibleButton from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
@ -32,36 +32,39 @@ interface IProps {
const AddExistingSubspaceDialog: React.FC<IProps> = ({ space, onCreateSubspaceClick, onFinished }) => {
const [selectedSpace, setSelectedSpace] = useState(space);
return <BaseDialog
title={(
<SubspaceSelector
title={_t("Add existing space")}
space={space}
value={selectedSpace}
onChange={setSelectedSpace}
/>
)}
className="mx_AddExistingToSpaceDialog"
contentId="mx_AddExistingToSpace"
onFinished={onFinished}
fixedWidth={false}
>
<MatrixClientContext.Provider value={space.client}>
<AddExistingToSpace
space={space}
onFinished={onFinished}
footerPrompt={<>
<div>{ _t("Want to add a new space instead?") }</div>
<AccessibleButton onClick={onCreateSubspaceClick} kind="link">
{ _t("Create a new space") }
</AccessibleButton>
</>}
filterPlaceholder={_t("Search for spaces")}
spacesRenderer={defaultSpacesRenderer}
/>
</MatrixClientContext.Provider>
</BaseDialog>;
return (
<BaseDialog
title={
<SubspaceSelector
title={_t("Add existing space")}
space={space}
value={selectedSpace}
onChange={setSelectedSpace}
/>
}
className="mx_AddExistingToSpaceDialog"
contentId="mx_AddExistingToSpace"
onFinished={onFinished}
fixedWidth={false}
>
<MatrixClientContext.Provider value={space.client}>
<AddExistingToSpace
space={space}
onFinished={onFinished}
footerPrompt={
<>
<div>{_t("Want to add a new space instead?")}</div>
<AccessibleButton onClick={onCreateSubspaceClick} kind="link">
{_t("Create a new space")}
</AccessibleButton>
</>
}
filterPlaceholder={_t("Search for spaces")}
spacesRenderer={defaultSpacesRenderer}
/>
</MatrixClientContext.Provider>
</BaseDialog>
);
};
export default AddExistingSubspaceDialog;

View file

@ -21,7 +21,7 @@ import { sleep } from "matrix-js-sdk/src/utils";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";
import { _t, _td } from '../../../languageHandler';
import { _t, _td } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
import Dropdown from "../elements/Dropdown";
import SearchBox from "../../structures/SearchBox";
@ -53,18 +53,21 @@ interface IProps {
}
export const Entry = ({ room, checked, onChange }) => {
return <label className="mx_AddExistingToSpace_entry">
{ room?.isSpaceRoom()
? <RoomAvatar room={room} height={32} width={32} />
: <DecoratedRoomAvatar room={room} avatarSize={32} />
}
<span className="mx_AddExistingToSpace_entry_name">{ room.name }</span>
<StyledCheckbox
onChange={onChange ? (e) => onChange(e.target.checked) : null}
checked={checked}
disabled={!onChange}
/>
</label>;
return (
<label className="mx_AddExistingToSpace_entry">
{room?.isSpaceRoom() ? (
<RoomAvatar room={room} height={32} width={32} />
) : (
<DecoratedRoomAvatar room={room} avatarSize={32} />
)}
<span className="mx_AddExistingToSpace_entry_name">{room.name}</span>
<StyledCheckbox
onChange={onChange ? (e) => onChange(e.target.checked) : null}
checked={checked}
disabled={!onChange}
/>
</label>
);
};
type OnChangeFn = (checked: boolean, room: Room) => void;
@ -98,14 +101,14 @@ const getScrollState = (
...prevGroupSizes: number[]
): IScrollState => {
let heightBefore = 0;
prevGroupSizes.forEach(size => {
heightBefore += GROUP_MARGIN + HEADER_HEIGHT + (size * ROW_HEIGHT);
prevGroupSizes.forEach((size) => {
heightBefore += GROUP_MARGIN + HEADER_HEIGHT + size * ROW_HEIGHT;
});
const viewportTop = scrollTop;
const viewportBottom = viewportTop + height;
const listTop = heightBefore + HEADER_HEIGHT;
const listBottom = listTop + (numItems * ROW_HEIGHT);
const listBottom = listTop + numItems * ROW_HEIGHT;
const top = Math.max(viewportTop, listTop);
const bottom = Math.min(viewportBottom, listBottom);
// the viewport height and scrollTop passed to the LazyRenderList
@ -128,7 +131,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
onFinished,
}) => {
const cli = useContext(MatrixClientContext);
const visibleRooms = useMemo(() => cli.getVisibleRooms().filter(r => r.getMyMembership() === "join"), [cli]);
const visibleRooms = useMemo(() => cli.getVisibleRooms().filter((r) => r.getMyMembership() === "join"), [cli]);
const scrollRef = useRef<AutoHideScrollbar<"div">>();
const [scrollState, setScrollState] = useState<IScrollState>({
@ -152,7 +155,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
if (lcQuery) {
const matcher = new QueryMatcher<Room>(visibleRooms, {
keys: ["name"],
funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
shouldMatchWordsOnly: false,
});
@ -160,21 +163,24 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
}
const joinRule = space.getJoinRule();
return sortRooms(rooms).reduce((arr, room) => {
if (room.isSpaceRoom()) {
if (room !== space && !existingSubspacesSet.has(room)) {
arr[0].push(room);
return sortRooms(rooms).reduce(
(arr, room) => {
if (room.isSpaceRoom()) {
if (room !== space && !existingSubspacesSet.has(room)) {
arr[0].push(room);
}
} else if (!existingRoomsSet.has(room)) {
if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
arr[1].push(room);
} else if (joinRule !== "public") {
// Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones.
arr[2].push(room);
}
}
} else if (!existingRoomsSet.has(room)) {
if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
arr[1].push(room);
} else if (joinRule !== "public") {
// Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones.
arr[2].push(room);
}
}
return arr;
}, [[], [], []]);
return arr;
},
[[], [], []],
);
}, [visibleRooms, space, lcQuery, existingRoomsSet, existingSubspacesSet]);
const addRooms = async () => {
@ -186,7 +192,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
for (const room of selectedToAdd) {
const via = calculateRoomVia(room);
try {
await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => {
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
@ -194,7 +200,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
throw e;
});
setProgress(i => i + 1);
setProgress((i) => i + 1);
} catch (e) {
logger.error("Failed to add rooms to space", e);
error = e;
@ -213,61 +219,70 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
let footer;
if (error) {
footer = <>
<img
src={require("../../../../res/img/element-icons/warning-badge.svg").default}
height="24"
width="24"
alt=""
/>
footer = (
<>
<img
src={require("../../../../res/img/element-icons/warning-badge.svg").default}
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>
<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>
</>;
<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>;
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 {
let button = emptySelectionButton;
if (!button || selectedToAdd.size > 0) {
button = <AccessibleButton kind="primary" disabled={selectedToAdd.size < 1} onClick={addRooms}>
{ _t("Add") }
</AccessibleButton>;
button = (
<AccessibleButton kind="primary" disabled={selectedToAdd.size < 1} onClick={addRooms}>
{_t("Add")}
</AccessibleButton>
);
}
footer = <>
<span>
{ footerPrompt }
</span>
footer = (
<>
<span>{footerPrompt}</span>
{ button }
</>;
{button}
</>
);
}
const onChange = !busy && !error ? (checked: boolean, room: Room) => {
if (checked) {
selectedToAdd.add(room);
} else {
selectedToAdd.delete(room);
}
setSelectedToAdd(new Set(selectedToAdd));
} : null;
const onChange =
!busy && !error
? (checked: boolean, room: Room) => {
if (checked) {
selectedToAdd.add(room);
} else {
selectedToAdd.delete(room);
}
setSelectedToAdd(new Set(selectedToAdd));
}
: null;
// only count spaces when alone as they're shown on a separate modal all on their own
const numSpaces = (spacesRenderer && !dmsRenderer && !roomsRenderer) ? spaces.length : 0;
const numSpaces = spacesRenderer && !dmsRenderer && !roomsRenderer ? spaces.length : 0;
const numRooms = roomsRenderer ? rooms.length : 0;
const numDms = dmsRenderer ? dms.length : 0;
@ -295,68 +310,66 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
const spacesScrollState = getScrollState(scrollState, numSpaces, numRooms);
const dmsScrollState = getScrollState(scrollState, numDms, numSpaces, numRooms);
return <div className="mx_AddExistingToSpace">
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={filterPlaceholder}
onSearch={setQuery}
autoFocus={true}
/>
<AutoHideScrollbar
className="mx_AddExistingToSpace_content"
onScroll={onScroll}
wrappedRef={wrappedRef}
ref={scrollRef}
>
{ rooms.length > 0 && roomsRenderer ? (
roomsRenderer(rooms, selectedToAdd, roomsScrollState, onChange)
) : undefined }
return (
<div className="mx_AddExistingToSpace">
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={filterPlaceholder}
onSearch={setQuery}
autoFocus={true}
/>
<AutoHideScrollbar
className="mx_AddExistingToSpace_content"
onScroll={onScroll}
wrappedRef={wrappedRef}
ref={scrollRef}
>
{rooms.length > 0 && roomsRenderer
? roomsRenderer(rooms, selectedToAdd, roomsScrollState, onChange)
: undefined}
{ spaces.length > 0 && spacesRenderer ? (
spacesRenderer(spaces, selectedToAdd, spacesScrollState, onChange)
) : null }
{spaces.length > 0 && spacesRenderer
? spacesRenderer(spaces, selectedToAdd, spacesScrollState, onChange)
: null}
{ dms.length > 0 && dmsRenderer ? (
dmsRenderer(dms, selectedToAdd, dmsScrollState, onChange)
) : null }
{dms.length > 0 && dmsRenderer ? dmsRenderer(dms, selectedToAdd, dmsScrollState, onChange) : null}
{ noResults ? <span className="mx_AddExistingToSpace_noResults">
{ _t("No results") }
</span> : undefined }
</AutoHideScrollbar>
{noResults ? <span className="mx_AddExistingToSpace_noResults">{_t("No results")}</span> : undefined}
</AutoHideScrollbar>
<div className="mx_AddExistingToSpace_footer">
{ footer }
<div className="mx_AddExistingToSpace_footer">{footer}</div>
</div>
</div>;
);
};
const defaultRendererFactory = (title: string): Renderer => (
rooms,
selectedToAdd,
{ scrollTop, height },
onChange,
) => (
<div className="mx_AddExistingToSpace_section">
<h3>{ _t(title) }</h3>
<LazyRenderList
itemHeight={ROW_HEIGHT}
items={rooms}
scrollTop={scrollTop}
height={height}
renderItem={room => (
<Entry
key={room.roomId}
room={room}
checked={selectedToAdd.has(room)}
onChange={onChange ? (checked: boolean) => {
onChange(checked, room);
} : null}
const defaultRendererFactory =
(title: string): Renderer =>
(rooms, selectedToAdd, { scrollTop, height }, onChange) =>
(
<div className="mx_AddExistingToSpace_section">
<h3>{_t(title)}</h3>
<LazyRenderList
itemHeight={ROW_HEIGHT}
items={rooms}
scrollTop={scrollTop}
height={height}
renderItem={(room) => (
<Entry
key={room.roomId}
room={room}
checked={selectedToAdd.has(room)}
onChange={
onChange
? (checked: boolean) => {
onChange(checked, room);
}
: null
}
/>
)}
/>
)}
/>
</div>
);
</div>
);
export const defaultRoomsRenderer = defaultRendererFactory(_td("Rooms"));
export const defaultSpacesRenderer = defaultRendererFactory(_td("Spaces"));
@ -371,9 +384,12 @@ interface ISubspaceSelectorProps {
export const SubspaceSelector = ({ title, space, value, onChange }: ISubspaceSelectorProps) => {
const options = useMemo(() => {
return [space, ...SpaceStore.instance.getChildSpaces(space.roomId).filter(space => {
return space.currentState.maySendStateEvent(EventType.SpaceChild, space.client.credentials.userId);
})];
return [
space,
...SpaceStore.instance.getChildSpaces(space.roomId).filter((space) => {
return space.currentState.maySendStateEvent(EventType.SpaceChild, space.client.credentials.userId);
}),
];
}, [space]);
let body;
@ -383,93 +399,100 @@ export const SubspaceSelector = ({ title, space, value, onChange }: ISubspaceSel
id="mx_SpaceSelectDropdown"
className="mx_SpaceSelectDropdown"
onOptionChange={(key: string) => {
onChange(options.find(space => space.roomId === key) || space);
onChange(options.find((space) => space.roomId === key) || space);
}}
value={value.roomId}
label={_t("Space selection")}
>
{ options.map((space) => {
{options.map((space) => {
const classes = classNames({
mx_SubspaceSelector_dropdownOptionActive: space === value,
});
return <div key={space.roomId} className={classes}>
<RoomAvatar room={space} width={24} height={24} />
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
</div>;
}) }
return (
<div key={space.roomId} className={classes}>
<RoomAvatar room={space} width={24} height={24} />
{space.name || getDisplayAliasForRoom(space) || space.roomId}
</div>
);
})}
</Dropdown>
);
} else {
body = (
<div className="mx_SubspaceSelector_onlySpace">
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
{space.name || getDisplayAliasForRoom(space) || space.roomId}
</div>
);
}
return <div className="mx_SubspaceSelector">
<RoomAvatar room={value} height={40} width={40} />
<div>
<h1>{ title }</h1>
{ body }
return (
<div className="mx_SubspaceSelector">
<RoomAvatar room={value} height={40} width={40} />
<div>
<h1>{title}</h1>
{body}
</div>
</div>
</div>;
);
};
const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick, onAddSubspaceClick, onFinished }) => {
const [selectedSpace, setSelectedSpace] = useState(space);
return <BaseDialog
title={(
<SubspaceSelector
title={_t("Add existing rooms")}
space={space}
value={selectedSpace}
onChange={setSelectedSpace}
/>
)}
className="mx_AddExistingToSpaceDialog"
contentId="mx_AddExistingToSpace"
onFinished={onFinished}
fixedWidth={false}
>
<MatrixClientContext.Provider value={space.client}>
<AddExistingToSpace
space={space}
onFinished={onFinished}
footerPrompt={<>
<div>{ _t("Want to add a new room instead?") }</div>
<AccessibleButton
kind="link"
onClick={(ev: ButtonEvent) => {
onCreateRoomClick(ev);
onFinished();
}}
>
{ _t("Create a new room") }
</AccessibleButton>
</>}
filterPlaceholder={_t("Search for rooms")}
roomsRenderer={defaultRoomsRenderer}
spacesRenderer={() => (
<div className="mx_AddExistingToSpace_section">
<h3>{ _t("Spaces") }</h3>
<AccessibleButton
kind="link"
onClick={() => {
onAddSubspaceClick();
onFinished();
}}
>
{ _t("Adding spaces has moved.") }
</AccessibleButton>
</div>
)}
dmsRenderer={defaultDmsRenderer}
/>
</MatrixClientContext.Provider>
</BaseDialog>;
return (
<BaseDialog
title={
<SubspaceSelector
title={_t("Add existing rooms")}
space={space}
value={selectedSpace}
onChange={setSelectedSpace}
/>
}
className="mx_AddExistingToSpaceDialog"
contentId="mx_AddExistingToSpace"
onFinished={onFinished}
fixedWidth={false}
>
<MatrixClientContext.Provider value={space.client}>
<AddExistingToSpace
space={space}
onFinished={onFinished}
footerPrompt={
<>
<div>{_t("Want to add a new room instead?")}</div>
<AccessibleButton
kind="link"
onClick={(ev: ButtonEvent) => {
onCreateRoomClick(ev);
onFinished();
}}
>
{_t("Create a new room")}
</AccessibleButton>
</>
}
filterPlaceholder={_t("Search for rooms")}
roomsRenderer={defaultRoomsRenderer}
spacesRenderer={() => (
<div className="mx_AddExistingToSpace_section">
<h3>{_t("Spaces")}</h3>
<AccessibleButton
kind="link"
onClick={() => {
onAddSubspaceClick();
onFinished();
}}
>
{_t("Adding spaces has moved.")}
</AccessibleButton>
</div>
)}
dmsRenderer={defaultDmsRenderer}
/>
</MatrixClientContext.Provider>
</BaseDialog>
);
};
export default AddExistingToSpaceDialog;

View file

@ -47,61 +47,83 @@ export const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
}) => {
const onPrimaryButtonClick = () => onFinished && onFinished(ButtonClicked.Primary);
const onCancelButtonClick = () => onFinished && onFinished(ButtonClicked.Cancel);
const privacyPolicyLink = privacyPolicyUrl ?
const privacyPolicyLink = privacyPolicyUrl ? (
<span>
{
_t("You can read all our terms <PrivacyPolicyUrl>here</PrivacyPolicyUrl>", {}, {
"PrivacyPolicyUrl": (sub) => {
return <a href={privacyPolicyUrl}
rel="norefferer noopener"
target="_blank"
>
{ sub }
<span className="mx_AnalyticsPolicyLink" />
</a>;
{_t(
"You can read all our terms <PrivacyPolicyUrl>here</PrivacyPolicyUrl>",
{},
{
PrivacyPolicyUrl: (sub) => {
return (
<a href={privacyPolicyUrl} rel="norefferer noopener" target="_blank">
{sub}
<span className="mx_AnalyticsPolicyLink" />
</a>
);
},
})
}
</span> : "";
return <BaseDialog
className="mx_AnalyticsLearnMoreDialog"
contentId="mx_AnalyticsLearnMore"
title={_t("Help improve %(analyticsOwner)s", { analyticsOwner })}
onFinished={onFinished}
>
<div className="mx_Dialog_content">
<div className="mx_AnalyticsLearnMore_image_holder" />
<div className="mx_AnalyticsLearnMore_copy">
{ _t("Help us identify issues and improve %(analyticsOwner)s by sharing anonymous usage data. " +
"To understand how people use multiple devices, we'll generate a random identifier, " +
"shared by your devices.", { analyticsOwner },
) }
},
)}
</span>
) : (
""
);
return (
<BaseDialog
className="mx_AnalyticsLearnMoreDialog"
contentId="mx_AnalyticsLearnMore"
title={_t("Help improve %(analyticsOwner)s", { analyticsOwner })}
onFinished={onFinished}
>
<div className="mx_Dialog_content">
<div className="mx_AnalyticsLearnMore_image_holder" />
<div className="mx_AnalyticsLearnMore_copy">
{_t(
"Help us identify issues and improve %(analyticsOwner)s by sharing anonymous usage data. " +
"To understand how people use multiple devices, we'll generate a random identifier, " +
"shared by your devices.",
{ analyticsOwner },
)}
</div>
<ul className="mx_AnalyticsLearnMore_bullets">
<li>
{_t(
"We <Bold>don't</Bold> record or profile any account data",
{},
{ Bold: (sub) => <b>{sub}</b> },
)}
</li>
<li>
{_t(
"We <Bold>don't</Bold> share information with third parties",
{},
{ Bold: (sub) => <b>{sub}</b> },
)}
</li>
<li>{_t("You can turn this off anytime in settings")}</li>
</ul>
{privacyPolicyLink}
</div>
<ul className="mx_AnalyticsLearnMore_bullets">
<li>{ _t("We <Bold>don't</Bold> record or profile any account data",
{}, { "Bold": (sub) => <b>{ sub }</b> }) }</li>
<li>{ _t("We <Bold>don't</Bold> share information with third parties",
{}, { "Bold": (sub) => <b>{ sub }</b> }) }</li>
<li>{ _t("You can turn this off anytime in settings") }</li>
</ul>
{ privacyPolicyLink }
</div>
<DialogButtons
primaryButton={primaryButton}
cancelButton={cancelButton}
onPrimaryButtonClick={onPrimaryButtonClick}
onCancel={onCancelButtonClick}
hasCancel={hasCancel}
/>
</BaseDialog>;
<DialogButtons
primaryButton={primaryButton}
cancelButton={cancelButton}
onPrimaryButtonClick={onPrimaryButtonClick}
onCancel={onCancelButtonClick}
hasCancel={hasCancel}
/>
</BaseDialog>
);
};
export const showDialog = (props: Omit<IProps, "cookiePolicyUrl" | "analyticsOwner">): void => {
const privacyPolicyUrl = getPolicyUrl();
const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get("brand");
Modal.createDialog(AnalyticsLearnMoreDialog, {
privacyPolicyUrl,
analyticsOwner,
...props,
}, "mx_AnalyticsLearnMoreDialog_wrapper");
Modal.createDialog(
AnalyticsLearnMoreDialog,
{
privacyPolicyUrl,
analyticsOwner,
...props,
},
"mx_AnalyticsLearnMoreDialog_wrapper",
);
};

View file

@ -47,59 +47,61 @@ export const AppDownloadDialog: FC<IDialogProps> = ({ onFinished }: IDialogProps
title={_t("Download %(brand)s", { brand })}
className="mx_AppDownloadDialog"
fixedWidth
onFinished={onFinished}>
{ desktopBuilds?.get("available") && (
onFinished={onFinished}
>
{desktopBuilds?.get("available") && (
<div className="mx_AppDownloadDialog_desktop">
<Heading size="h3">
{ _t("Download %(brand)s Desktop", { brand }) }
</Heading>
<Heading size="h3">{_t("Download %(brand)s Desktop", { brand })}</Heading>
<AccessibleButton
kind="primary"
element="a"
href={desktopBuilds?.get("url")}
target="_blank"
onClick={() => {}}>
{ _t("Download %(brand)s Desktop", { brand }) }
onClick={() => {}}
>
{_t("Download %(brand)s Desktop", { brand })}
</AccessibleButton>
</div>
) }
)}
<div className="mx_AppDownloadDialog_mobile">
<div className="mx_AppDownloadDialog_app">
<Heading size="h3">
{ _t("iOS") }
</Heading>
<Heading size="h3">{_t("iOS")}</Heading>
<QRCode data={urlAppStore} margin={0} width={172} />
<div className="mx_AppDownloadDialog_info">{ _t("%(qrCode)s or %(appLinks)s", {
appLinks: "",
qrCode: "",
}) }</div>
<div className="mx_AppDownloadDialog_info">
{_t("%(qrCode)s or %(appLinks)s", {
appLinks: "",
qrCode: "",
})}
</div>
<div className="mx_AppDownloadDialog_links">
<AccessibleButton
element="a"
href={urlAppStore}
target="_blank"
aria-label={_t("Download on the App Store")}
onClick={() => {}}>
onClick={() => {}}
>
<IOSBadge />
</AccessibleButton>
</div>
</div>
<div className="mx_AppDownloadDialog_app">
<Heading size="h3">
{ _t("Android") }
</Heading>
<Heading size="h3">{_t("Android")}</Heading>
<QRCode data={urlAndroid} margin={0} width={172} />
<div className="mx_AppDownloadDialog_info">{ _t("%(qrCode)s or %(appLinks)s", {
appLinks: "",
qrCode: "",
}) }</div>
<div className="mx_AppDownloadDialog_info">
{_t("%(qrCode)s or %(appLinks)s", {
appLinks: "",
qrCode: "",
})}
</div>
<div className="mx_AppDownloadDialog_links">
<AccessibleButton
element="a"
href={urlGooglePlay}
target="_blank"
aria-label={_t("Get it on Google Play")}
onClick={() => {}}>
onClick={() => {}}
>
<GooglePlayBadge />
</AccessibleButton>
<AccessibleButton
@ -107,15 +109,16 @@ export const AppDownloadDialog: FC<IDialogProps> = ({ onFinished }: IDialogProps
href={urlFDroid}
target="_blank"
aria-label={_t("Get it on F-Droid")}
onClick={() => {}}>
onClick={() => {}}
>
<FDroidBadge />
</AccessibleButton>
</div>
</div>
</div>
<div className="mx_AppDownloadDialog_legal">
<p>{ _t("App Store® and the Apple logo® are trademarks of Apple Inc.") }</p>
<p>{ _t("Google Play and the Google Play logo are trademarks of Google LLC.") }</p>
<p>{_t("App Store® and the Apple logo® are trademarks of Apple Inc.")}</p>
<p>{_t("Google Play and the Google Play logo are trademarks of Google LLC.")}</p>
</div>
</BaseDialog>
);

View file

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import BaseDialog from "./BaseDialog";
@ -49,32 +49,36 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
};
public render() {
const errorList = this.props.unknownProfileUsers
.map(address => <li key={address.userId}>{ address.userId }: { address.errorText }</li>);
const errorList = this.props.unknownProfileUsers.map((address) => (
<li key={address.userId}>
{address.userId}: {address.errorText}
</li>
));
return (
<BaseDialog className='mx_RetryInvitesDialog'
<BaseDialog
className="mx_RetryInvitesDialog"
onFinished={this.onGiveUpClicked}
title={_t('The following users may not exist')}
contentId='mx_Dialog_content'
title={_t("The following users may not exist")}
contentId="mx_Dialog_content"
>
<div id='mx_Dialog_content'>
<p>{ _t("Unable to find profiles for the Matrix IDs listed below - " +
"would you like to invite them anyway?") }</p>
<ul>
{ errorList }
</ul>
<div id="mx_Dialog_content">
<p>
{_t(
"Unable to find profiles for the Matrix IDs listed below - " +
"would you like to invite them anyway?",
)}
</p>
<ul>{errorList}</ul>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onGiveUpClicked}>
{ _t('Close') }
</button>
<button onClick={this.onGiveUpClicked}>{_t("Close")}</button>
<button onClick={this.onInviteNeverWarnClicked}>
{ _t('Invite anyway and never warn me again') }
{_t("Invite anyway and never warn me again")}
</button>
<button onClick={this.onInviteClicked} autoFocus={true}>
{ _t('Invite anyway') }
{_t("Invite anyway")}
</button>
</div>
</BaseDialog>

View file

@ -16,16 +16,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import FocusLock from 'react-focus-lock';
import classNames from 'classnames';
import React from "react";
import FocusLock from "react-focus-lock";
import classNames from "classnames";
import { MatrixClient } from "matrix-js-sdk/src/client";
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import Heading from '../typography/Heading';
import Heading from "../typography/Heading";
import { IDialogProps } from "./IDialogProps";
import { PosthogScreenTracker, ScreenName } from "../../../PosthogTrackers";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
@ -37,41 +37,41 @@ interface IProps extends IDialogProps {
// to false if there is nothing the app can sensibly do if the
// dialog is cancelled, eg. "We can't restore your session and
// the app cannot work". Default: true.
hasCancel?: boolean;
"hasCancel"?: boolean;
// called when a key is pressed
onKeyDown?: (e: KeyboardEvent | React.KeyboardEvent) => void;
"onKeyDown"?: (e: KeyboardEvent | React.KeyboardEvent) => void;
// CSS class to apply to dialog div
className?: string;
"className"?: string;
// if true, dialog container is 60% of the viewport width. Otherwise,
// the container will have no fixed size, allowing its contents to
// determine its size. Default: true.
fixedWidth?: boolean;
"fixedWidth"?: boolean;
// Title for the dialog.
title?: JSX.Element | string;
"title"?: JSX.Element | string;
// Specific aria label to use, if not provided will set aria-labelledBy to mx_Dialog_title
"aria-label"?: string;
// Path to an icon to put in the header
headerImage?: string;
"headerImage"?: string;
// children should be the content of the dialog
children?: React.ReactNode;
"children"?: React.ReactNode;
// Id of content element
// If provided, this is used to add a aria-describedby attribute
contentId?: string;
"contentId"?: string;
// optional additional class for the title element (basically anything that can be passed to classnames)
titleClass?: string | string[];
"titleClass"?: string | string[];
headerButton?: JSX.Element;
"headerButton"?: JSX.Element;
// optional Posthog ScreenName to supply during the lifetime of this dialog
screenName?: ScreenName;
"screenName"?: ScreenName;
}
/*
@ -119,7 +119,11 @@ export default class BaseDialog extends React.Component<IProps> {
let cancelButton;
if (this.props.hasCancel) {
cancelButton = (
<AccessibleButton onClick={this.onCancelClick} className="mx_Dialog_cancelButton" aria-label={_t("Close dialog")} />
<AccessibleButton
onClick={this.onCancelClick}
className="mx_Dialog_cancelButton"
aria-label={_t("Close dialog")}
/>
);
}
@ -154,21 +158,27 @@ export default class BaseDialog extends React.Component<IProps> {
lockProps={lockProps}
className={classNames({
[this.props.className]: true,
'mx_Dialog_fixedWidth': this.props.fixedWidth,
mx_Dialog_fixedWidth: this.props.fixedWidth,
})}
>
<div className={classNames('mx_Dialog_header', {
'mx_Dialog_headerWithButton': !!this.props.headerButton,
'mx_Dialog_headerWithCancel': !!cancelButton,
})}>
<Heading size='h2' className={classNames('mx_Dialog_title', this.props.titleClass)} id='mx_BaseDialog_title'>
{ headerImage }
{ this.props.title }
<div
className={classNames("mx_Dialog_header", {
mx_Dialog_headerWithButton: !!this.props.headerButton,
mx_Dialog_headerWithCancel: !!cancelButton,
})}
>
<Heading
size="h2"
className={classNames("mx_Dialog_title", this.props.titleClass)}
id="mx_BaseDialog_title"
>
{headerImage}
{this.props.title}
</Heading>
{ this.props.headerButton }
{ cancelButton }
{this.props.headerButton}
{cancelButton}
</div>
{ this.props.children }
{this.props.children}
</FocusLock>
</MatrixClientContext.Provider>
);

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from "react";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
@ -34,28 +34,32 @@ interface IProps extends IDialogProps {
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
const info = SettingsStore.getBetaInfo(featureId);
return <GenericFeatureFeedbackDialog
title={_t("%(featureName)s Beta feedback", { featureName: info.title })}
subheading={_t(info.feedbackSubheading)}
onFinished={onFinished}
rageshakeLabel={info.feedbackLabel}
rageshakeData={Object.fromEntries((SettingsStore.getBetaInfo(featureId)?.extraSettings || []).map(k => {
return SettingsStore.getValue(k);
}))}
>
<AccessibleButton
kind="link_inline"
onClick={() => {
onFinished(false);
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
});
}}
return (
<GenericFeatureFeedbackDialog
title={_t("%(featureName)s Beta feedback", { featureName: info.title })}
subheading={_t(info.feedbackSubheading)}
onFinished={onFinished}
rageshakeLabel={info.feedbackLabel}
rageshakeData={Object.fromEntries(
(SettingsStore.getBetaInfo(featureId)?.extraSettings || []).map((k) => {
return SettingsStore.getValue(k);
}),
)}
>
{ _t("To leave the beta, visit your settings.") }
</AccessibleButton>
</GenericFeatureFeedbackDialog>;
<AccessibleButton
kind="link_inline"
onClick={() => {
onFinished(false);
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
});
}}
>
{_t("To leave the beta, visit your settings.")}
</AccessibleButton>
</GenericFeatureFeedbackDialog>
);
};
export default BetaFeedbackDialog;

View file

@ -17,21 +17,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import sendBugReport, { downloadBugReport } from '../../../rageshake/submit-rageshake';
import SdkConfig from "../../../SdkConfig";
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import sendBugReport, { downloadBugReport } from "../../../rageshake/submit-rageshake";
import AccessibleButton from "../elements/AccessibleButton";
import QuestionDialog from "./QuestionDialog";
import BaseDialog from "./BaseDialog";
import Field from '../elements/Field';
import Field from "../elements/Field";
import Spinner from "../elements/Spinner";
import DialogButtons from "../elements/DialogButtons";
import { sendSentryReport } from "../../../sentry";
import defaultDispatcher from '../../../dispatcher/dispatcher';
import { Action } from '../../../dispatcher/actions';
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
interface IProps {
onFinished: (success: boolean) => void;
@ -96,8 +96,9 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
}
const userText =
(this.state.text.length > 0 ? this.state.text + '\n\n': '') + 'Issue: ' +
(this.state.issueUrl.length > 0 ? this.state.issueUrl : 'No issue link given');
(this.state.text.length > 0 ? this.state.text + "\n\n" : "") +
"Issue: " +
(this.state.issueUrl.length > 0 ? this.state.issueUrl : "No issue link given");
this.setState({ busy: true, progress: null, err: null });
this.sendProgressCallback(_t("Preparing to send logs"));
@ -107,24 +108,27 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
sendLogs: true,
progressCallback: this.sendProgressCallback,
labels: this.props.label ? [this.props.label] : [],
}).then(() => {
if (!this.unmounted) {
this.props.onFinished(false);
Modal.createDialog(QuestionDialog, {
title: _t('Logs sent'),
description: _t('Thank you!'),
hasCancelButton: false,
});
}
}, (err) => {
if (!this.unmounted) {
this.setState({
busy: false,
progress: null,
err: _t("Failed to send logs: ") + `${err.message}`,
});
}
});
}).then(
() => {
if (!this.unmounted) {
this.props.onFinished(false);
Modal.createDialog(QuestionDialog, {
title: _t("Logs sent"),
description: _t("Thank you!"),
hasCancelButton: false,
});
}
},
(err) => {
if (!this.unmounted) {
this.setState({
busy: false,
progress: null,
err: _t("Failed to send logs: ") + `${err.message}`,
});
}
},
);
sendSentryReport(this.state.text, this.state.issueUrl, this.props.error);
};
@ -179,9 +183,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
public render() {
let error = null;
if (this.state.err) {
error = <div className="error">
{ this.state.err }
</div>;
error = <div className="error">{this.state.err}</div>;
}
let progress = null;
@ -189,55 +191,61 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
progress = (
<div className="progress">
<Spinner />
{ this.state.progress } ...
{this.state.progress} ...
</div>
);
}
let warning;
if (window.Modernizr && Object.values(window.Modernizr).some(support => support === false)) {
warning = <p><b>
{ _t("Reminder: Your browser is unsupported, so your experience may be unpredictable.") }
</b></p>;
if (window.Modernizr && Object.values(window.Modernizr).some((support) => support === false)) {
warning = (
<p>
<b>{_t("Reminder: Your browser is unsupported, so your experience may be unpredictable.")}</b>
</p>
);
}
return (
<BaseDialog
className="mx_BugReportDialog"
onFinished={this.onCancel}
title={_t('Submit debug logs')}
contentId='mx_Dialog_content'
title={_t("Submit debug logs")}
contentId="mx_Dialog_content"
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ warning }
<div className="mx_Dialog_content" id="mx_Dialog_content">
{warning}
<p>
{ _t(
{_t(
"Debug logs contain application usage data including your " +
"username, the IDs or aliases of the rooms you " +
"have visited, which UI elements you last interacted with, " +
"and the usernames of other users. They do not contain messages.",
) }
"username, the IDs or aliases of the rooms you " +
"have visited, which UI elements you last interacted with, " +
"and the usernames of other users. They do not contain messages.",
)}
</p>
<p>
<b>
{_t(
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.",
{},
{
a: (sub) => (
<a
target="_blank"
href="https://github.com/vector-im/element-web/issues/new/choose"
>
{sub}
</a>
),
},
)}
</b>
</p>
<p><b>
{ _t(
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.",
{},
{
a: (sub) => <a
target="_blank"
href="https://github.com/vector-im/element-web/issues/new/choose"
>
{ sub }
</a>,
},
) }
</b></p>
<div className="mx_BugReportDialog_download">
<AccessibleButton onClick={this.onDownload} kind="link" disabled={this.state.downloadBusy}>
{ _t("Download logs") }
{_t("Download logs")}
</AccessibleButton>
{ this.state.downloadProgress && <span>{ this.state.downloadProgress } ...</span> }
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
</div>
<Field
@ -257,15 +265,16 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
value={this.state.text}
placeholder={_t(
"If there is additional context that would help in " +
"analysing the issue, such as what you were doing at " +
"the time, room IDs, user IDs, etc., " +
"please include those things here.",
"analysing the issue, such as what you were doing at " +
"the time, room IDs, user IDs, etc., " +
"please include those things here.",
)}
/>
{ progress }
{ error }
{progress}
{error}
</div>
<DialogButtons primaryButton={_t("Send logs")}
<DialogButtons
primaryButton={_t("Send logs")}
onPrimaryButtonClick={this.onSubmit}
focus={true}
onCancel={this.onCancel}

View file

@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useState } from 'react';
import React, { useState } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { Room } from 'matrix-js-sdk/src/models/room';
import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room";
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { IDialogProps } from "./IDialogProps";
@ -37,38 +37,45 @@ interface IBulkRedactDialogProps extends IDialogProps {
member: RoomMember;
}
const BulkRedactDialog: React.FC<IBulkRedactDialogProps> = props => {
const BulkRedactDialog: React.FC<IBulkRedactDialogProps> = (props) => {
const { matrixClient: cli, room, member, onFinished } = props;
const [keepStateEvents, setKeepStateEvents] = useState(true);
let timeline = room.getLiveTimeline();
let eventsToRedact = [];
while (timeline) {
eventsToRedact = [...eventsToRedact, ...timeline.getEvents().filter(event =>
event.getSender() === member.userId &&
!event.isRedacted() && !event.isRedaction() &&
event.getType() !== EventType.RoomCreate &&
// Don't redact ACLs because that'll obliterate the room
// See https://github.com/matrix-org/synapse/issues/4042 for details.
event.getType() !== EventType.RoomServerAcl &&
// Redacting encryption events is equally bad
event.getType() !== EventType.RoomEncryption,
)];
eventsToRedact = [
...eventsToRedact,
...timeline.getEvents().filter(
(event) =>
event.getSender() === member.userId &&
!event.isRedacted() &&
!event.isRedaction() &&
event.getType() !== EventType.RoomCreate &&
// Don't redact ACLs because that'll obliterate the room
// See https://github.com/matrix-org/synapse/issues/4042 for details.
event.getType() !== EventType.RoomServerAcl &&
// Redacting encryption events is equally bad
event.getType() !== EventType.RoomEncryption,
),
];
timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS);
}
if (eventsToRedact.length === 0) {
return <InfoDialog
onFinished={onFinished}
title={_t("No recent messages by %(user)s found", { user: member.name })}
description={
<div>
<p>{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }</p>
</div>
}
/>;
return (
<InfoDialog
onFinished={onFinished}
title={_t("No recent messages by %(user)s found", { user: member.name })}
description={
<div>
<p>{_t("Try scrolling up in the timeline to see if there are any earlier ones.")}</p>
</div>
}
/>
);
} else {
eventsToRedact = eventsToRedact.filter(event => !(keepStateEvents && event.isState()));
eventsToRedact = eventsToRedact.filter((event) => !(keepStateEvents && event.isState()));
const count = eventsToRedact.length;
const user = member.name;
@ -82,15 +89,17 @@ const BulkRedactDialog: React.FC<IBulkRedactDialogProps> = props => {
// Submitting a large number of redactions freezes the UI,
// so first yield to allow to rerender after closing the dialog.
await Promise.resolve();
await Promise.all(eventsToRedact.reverse().map(async event => {
try {
await cli.redactEvent(room.roomId, event.getId());
} catch (err) {
// log and swallow errors
logger.error("Could not redact", event.getId());
logger.error(err);
}
}));
await Promise.all(
eventsToRedact.reverse().map(async (event) => {
try {
await cli.redactEvent(room.roomId, event.getId());
} catch (err) {
// log and swallow errors
logger.error("Could not redact", event.getId());
logger.error(err);
}
}),
);
logger.info(`Finished redacting recent ${count} messages for ${member.userId} in ${room.roomId}`);
dis.dispatch({
@ -99,37 +108,50 @@ const BulkRedactDialog: React.FC<IBulkRedactDialogProps> = props => {
});
};
return <BaseDialog
className="mx_BulkRedactDialog"
onFinished={onFinished}
title={_t("Remove recent messages by %(user)s", { user })}
contentId="mx_Dialog_content"
>
<div className="mx_Dialog_content" id="mx_Dialog_content">
<p>{ _t("You are about to remove %(count)s messages by %(user)s. " +
"This will remove them permanently for everyone in the conversation. " +
"Do you wish to continue?", { count, user }) }</p>
<p>{ _t("For a large amount of messages, this might take some time. " +
"Please don't refresh your client in the meantime.") }</p>
<StyledCheckbox
checked={keepStateEvents}
onChange={e => setKeepStateEvents(e.target.checked)}
>
{ _t("Preserve system messages") }
</StyledCheckbox>
<div className="mx_BulkRedactDialog_checkboxMicrocopy">
{ _t("Uncheck if you also want to remove system messages on this user " +
"(e.g. membership change, profile change…)") }
return (
<BaseDialog
className="mx_BulkRedactDialog"
onFinished={onFinished}
title={_t("Remove recent messages by %(user)s", { user })}
contentId="mx_Dialog_content"
>
<div className="mx_Dialog_content" id="mx_Dialog_content">
<p>
{_t(
"You are about to remove %(count)s messages by %(user)s. " +
"This will remove them permanently for everyone in the conversation. " +
"Do you wish to continue?",
{ count, user },
)}
</p>
<p>
{_t(
"For a large amount of messages, this might take some time. " +
"Please don't refresh your client in the meantime.",
)}
</p>
<StyledCheckbox checked={keepStateEvents} onChange={(e) => setKeepStateEvents(e.target.checked)}>
{_t("Preserve system messages")}
</StyledCheckbox>
<div className="mx_BulkRedactDialog_checkboxMicrocopy">
{_t(
"Uncheck if you also want to remove system messages on this user " +
"(e.g. membership change, profile change…)",
)}
</div>
</div>
</div>
<DialogButtons
primaryButton={_t("Remove %(count)s messages", { count })}
primaryButtonClass="danger"
primaryDisabled={count === 0}
onPrimaryButtonClick={() => { setImmediate(redact); onFinished(true); }}
onCancel={() => onFinished(false)}
/>
</BaseDialog>;
<DialogButtons
primaryButton={_t("Remove %(count)s messages", { count })}
primaryButtonClass="danger"
primaryDisabled={count === 0}
onPrimaryButtonClick={() => {
setImmediate(redact);
onFinished(true);
}}
onCancel={() => onFinished(false)}
/>
</BaseDialog>
);
}
};

View file

@ -15,9 +15,9 @@ Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import QuestionDialog from "./QuestionDialog";
import Spinner from "../elements/Spinner";
@ -27,7 +27,7 @@ interface IProps {
onFinished: (success: boolean) => void;
}
const REPOS = ['vector-im/element-web', 'matrix-org/matrix-react-sdk', 'matrix-org/matrix-js-sdk'];
const REPOS = ["vector-im/element-web", "matrix-org/matrix-react-sdk", "matrix-org/matrix-js-sdk"];
export default class ChangelogDialog extends React.Component<IProps> {
constructor(props) {
@ -55,13 +55,13 @@ export default class ChangelogDialog extends React.Component<IProps> {
}
public componentDidMount() {
const version = this.props.newVersion.split('-');
const version2 = this.props.version.split('-');
const version = this.props.newVersion.split("-");
const version2 = this.props.version.split("-");
if (version == null || version2 == null) return;
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
for (let i = 0; i < REPOS.length; i++) {
const oldVersion = version2[2*i];
const newVersion = version[2*i];
const oldVersion = version2[2 * i];
const newVersion = version[2 * i];
this.fetchChanges(REPOS[i], oldVersion, newVersion);
}
}
@ -70,14 +70,14 @@ export default class ChangelogDialog extends React.Component<IProps> {
return (
<li key={commit.sha} className="mx_ChangelogDialog_li">
<a href={commit.html_url} target="_blank" rel="noreferrer noopener">
{ commit.commit.message.split('\n')[0] }
{commit.commit.message.split("\n")[0]}
</a>
</li>
);
}
public render() {
const logs = REPOS.map(repo => {
const logs = REPOS.map((repo) => {
let content;
if (this.state[repo] == null) {
content = <Spinner key={repo} />;
@ -90,15 +90,15 @@ export default class ChangelogDialog extends React.Component<IProps> {
}
return (
<div key={repo}>
<h2>{ repo }</h2>
<ul>{ content }</ul>
<h2>{repo}</h2>
<ul>{content}</ul>
</div>
);
});
const content = (
<div className="mx_ChangelogDialog_content">
{ this.props.version == null || this.props.newVersion == null ? <h2>{ _t("Unavailable") }</h2> : logs }
{this.props.version == null || this.props.newVersion == null ? <h2>{_t("Unavailable")}</h2> : logs}
</div>
);

View file

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from '../../../languageHandler';
import ConfirmRedactDialog from './ConfirmRedactDialog';
import ErrorDialog from './ErrorDialog';
import { _t } from "../../../languageHandler";
import ConfirmRedactDialog from "./ConfirmRedactDialog";
import ErrorDialog from "./ErrorDialog";
import BaseDialog from "./BaseDialog";
import Spinner from "../elements/Spinner";
@ -79,16 +79,13 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IPro
return (
<ErrorDialog
onFinished={this.props.onFinished}
title={_t('Error')}
description={_t('You cannot delete this message. (%(code)s)', { code })}
title={_t("Error")}
description={_t("You cannot delete this message. (%(code)s)", { code })}
/>
);
} else {
return (
<BaseDialog
onFinished={this.props.onFinished}
hasCancel={false}
title={_t("Removing…")}>
<BaseDialog onFinished={this.props.onFinished} hasCancel={false} title={_t("Removing…")}>
<Spinner />
</BaseDialog>
);

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import React from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import React from "react";
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import ErrorDialog from './ErrorDialog';
import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import Modal from "../../../Modal";
import ErrorDialog from "./ErrorDialog";
import TextInputDialog from "./TextInputDialog";
interface IProps {
@ -33,11 +33,13 @@ interface IProps {
export default class ConfirmRedactDialog extends React.Component<IProps> {
render() {
return (
<TextInputDialog onFinished={this.props.onFinished}
<TextInputDialog
onFinished={this.props.onFinished}
title={_t("Confirm Removal")}
description={
_t("Are you sure you wish to remove (delete) this event? " +
"Note that if you delete a room name or topic change, it could undo the change.")}
description={_t(
"Are you sure you wish to remove (delete) this event? " +
"Note that if you delete a room name or topic change, it could undo the change.",
)}
placeholder={_t("Reason (optional)")}
focus
button={_t("Remove")}
@ -53,32 +55,31 @@ export function createRedactEventDialog({
mxEvent: MatrixEvent;
onCloseDialog?: () => void;
}) {
Modal.createDialog(ConfirmRedactDialog, {
onFinished: async (proceed: boolean, reason?: string) => {
if (!proceed) return;
Modal.createDialog(
ConfirmRedactDialog,
{
onFinished: async (proceed: boolean, reason?: string) => {
if (!proceed) return;
const cli = MatrixClientPeg.get();
try {
onCloseDialog?.();
await cli.redactEvent(
mxEvent.getRoomId(),
mxEvent.getId(),
undefined,
reason ? { reason } : {},
);
} catch (e) {
const code = e.errcode || e.statusCode;
// only show the dialog if failing for something other than a network error
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
// detached queue and we show the room status bar to allow retry
if (typeof code !== "undefined") {
// display error message stating you couldn't delete this.
Modal.createDialog(ErrorDialog, {
title: _t('Error'),
description: _t('You cannot delete this message. (%(code)s)', { code }),
});
const cli = MatrixClientPeg.get();
try {
onCloseDialog?.();
await cli.redactEvent(mxEvent.getRoomId(), mxEvent.getId(), undefined, reason ? { reason } : {});
} catch (e) {
const code = e.errcode || e.statusCode;
// only show the dialog if failing for something other than a network error
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
// detached queue and we show the room status bar to allow retry
if (typeof code !== "undefined") {
// display error message stating you couldn't delete this.
Modal.createDialog(ErrorDialog, {
title: _t("Error"),
description: _t("You cannot delete this message. (%(code)s)", { code }),
});
}
}
}
},
},
}, 'mx_Dialog_confirmredact');
"mx_Dialog_confirmredact",
);
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ComponentProps, useMemo, useState } from 'react';
import React, { ComponentProps, useMemo, useState } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import ConfirmUserActionDialog from "./ConfirmUserActionDialog";
@ -55,9 +55,7 @@ const ConfirmSpaceUserActionDialog: React.FC<IProps> = ({
let warning: JSX.Element;
if (warningMessage) {
warning = <div className="mx_ConfirmSpaceUserActionDialog_warning">
{ warningMessage }
</div>;
warning = <div className="mx_ConfirmSpaceUserActionDialog_warning">{warningMessage}</div>;
}
return (
@ -69,7 +67,7 @@ const ConfirmSpaceUserActionDialog: React.FC<IProps> = ({
className="mx_ConfirmSpaceUserActionDialog"
roomId={space.roomId}
>
{ warning }
{warning}
<SpaceChildrenPicker
space={space}
spaceChildren={spaceChildren}

View file

@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ChangeEvent, FormEvent, ReactNode } from 'react';
import React, { ChangeEvent, FormEvent, ReactNode } from "react";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
import MemberAvatar from '../avatars/MemberAvatar';
import { _t } from "../../../languageHandler";
import MemberAvatar from "../avatars/MemberAvatar";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import Field from '../elements/Field';
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
import Field from "../elements/Field";
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
interface IProps {
// matrix-js-sdk (room) member object.
@ -84,7 +84,7 @@ export default class ConfirmUserActionDialog extends React.Component<IProps, ISt
};
public render() {
const confirmButtonClass = this.props.danger ? 'danger' : '';
const confirmButtonClass = this.props.danger ? "danger" : "";
let reasonBox;
if (this.props.askReason) {
@ -106,35 +106,35 @@ export default class ConfirmUserActionDialog extends React.Component<IProps, ISt
const name = this.props.member.name;
const userId = this.props.member.userId;
const displayUserIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(
userId, { roomId: this.props.roomId, withDisplayName: true },
);
const displayUserIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(userId, {
roomId: this.props.roomId,
withDisplayName: true,
});
return (
<BaseDialog
className={classNames("mx_ConfirmUserActionDialog", this.props.className)}
onFinished={this.props.onFinished}
title={this.props.title}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
>
<div id="mx_Dialog_content" className="mx_Dialog_content">
<div className="mx_ConfirmUserActionDialog_user">
<div className="mx_ConfirmUserActionDialog_avatar">
{ avatar }
</div>
<div className="mx_ConfirmUserActionDialog_name">{ name }</div>
<div className="mx_ConfirmUserActionDialog_userId">{ displayUserIdentifier }</div>
<div className="mx_ConfirmUserActionDialog_avatar">{avatar}</div>
<div className="mx_ConfirmUserActionDialog_name">{name}</div>
<div className="mx_ConfirmUserActionDialog_userId">{displayUserIdentifier}</div>
</div>
{ reasonBox }
{ this.props.children }
{reasonBox}
{this.props.children}
</div>
<DialogButtons
primaryButton={this.props.action}
onPrimaryButtonClick={this.onOk}
primaryButtonClass={confirmButtonClass}
focus={!this.props.askReason}
onCancel={this.onCancel} />
onCancel={this.onCancel}
/>
</BaseDialog>
);
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
@ -36,17 +36,17 @@ export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
render() {
return (
<BaseDialog
className='mx_ConfirmWipeDeviceDialog'
className="mx_ConfirmWipeDeviceDialog"
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Clear all data in this session?")}
>
<div className='mx_ConfirmWipeDeviceDialog_content'>
<div className="mx_ConfirmWipeDeviceDialog_content">
<p>
{ _t(
{_t(
"Clearing all data from this session is permanent. Encrypted messages will be lost " +
"unless their keys have been backed up.",
) }
"unless their keys have been backed up.",
)}
</p>
</div>
<DialogButtons

View file

@ -20,10 +20,10 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomType } from "matrix-js-sdk/src/@types/event";
import { JoinRule, Preset, Visibility } from "matrix-js-sdk/src/@types/partials";
import SdkConfig from '../../../SdkConfig';
import withValidation, { IFieldState } from '../elements/Validation';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import SdkConfig from "../../../SdkConfig";
import withValidation, { IFieldState } from "../elements/Validation";
import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { IOpts } from "../../../createRoom";
import Field from "../elements/Field";
import RoomAliasField from "../elements/RoomAliasField";
@ -87,13 +87,14 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
canChangeEncryption: true,
};
MatrixClientPeg.get().doesServerForceEncryptionForPreset(Preset.PrivateChat)
.then(isForced => this.setState({ canChangeEncryption: !isForced }));
MatrixClientPeg.get()
.doesServerForceEncryptionForPreset(Preset.PrivateChat)
.then((isForced) => this.setState({ canChangeEncryption: !isForced }));
}
private roomCreateOptions() {
const opts: IOpts = {};
const createOpts: IOpts["createOpts"] = opts.createOpts = {};
const createOpts: IOpts["createOpts"] = (opts.createOpts = {});
opts.roomType = this.props.type;
createOpts.name = this.state.name;
@ -112,7 +113,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
createOpts.topic = this.state.topic;
}
if (this.state.noFederate) {
createOpts.creation_content = { 'm.federate': false };
createOpts.creation_content = { "m.federate": false };
}
opts.parentSpace = this.props.parentSpace;
@ -150,7 +151,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
}
// Validation and state updates are async, so we need to wait for them to complete
// first. Queue a `setState` callback and wait for it to resolve.
await new Promise<void>(resolve => this.setState({}, resolve));
await new Promise<void>((resolve) => this.setState({}, resolve));
if (this.state.nameIsValid && (!this.aliasField.current || this.aliasField.current.isValid)) {
this.props.onFinished(true, this.roomCreateOptions());
} else {
@ -235,39 +236,49 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
let publicPrivateLabel: JSX.Element;
if (this.state.joinRule === JoinRule.Restricted) {
publicPrivateLabel = <p>
{ _t(
"Everyone in <SpaceName/> will be able to find and join this room.", {}, {
SpaceName: () => <b>{ this.props.parentSpace.name }</b>,
},
) }
&nbsp;
{ _t("You can change this at any time from room settings.") }
</p>;
publicPrivateLabel = (
<p>
{_t(
"Everyone in <SpaceName/> will be able to find and join this room.",
{},
{
SpaceName: () => <b>{this.props.parentSpace.name}</b>,
},
)}
&nbsp;
{_t("You can change this at any time from room settings.")}
</p>
);
} else if (this.state.joinRule === JoinRule.Public && this.props.parentSpace) {
publicPrivateLabel = <p>
{ _t(
"Anyone will be able to find and join this room, not just members of <SpaceName/>.", {}, {
SpaceName: () => <b>{ this.props.parentSpace.name }</b>,
},
) }
&nbsp;
{ _t("You can change this at any time from room settings.") }
</p>;
publicPrivateLabel = (
<p>
{_t(
"Anyone will be able to find and join this room, not just members of <SpaceName/>.",
{},
{
SpaceName: () => <b>{this.props.parentSpace.name}</b>,
},
)}
&nbsp;
{_t("You can change this at any time from room settings.")}
</p>
);
} else if (this.state.joinRule === JoinRule.Public) {
publicPrivateLabel = <p>
{ _t("Anyone will be able to find and join this room.") }
&nbsp;
{ _t("You can change this at any time from room settings.") }
</p>;
publicPrivateLabel = (
<p>
{_t("Anyone will be able to find and join this room.")}
&nbsp;
{_t("You can change this at any time from room settings.")}
</p>
);
} else if (this.state.joinRule === JoinRule.Invite) {
publicPrivateLabel = <p>
{ _t(
"Only people invited will be able to find and join this room.",
) }
&nbsp;
{ _t("You can change this at any time from room settings.") }
</p>;
publicPrivateLabel = (
<p>
{_t("Only people invited will be able to find and join this room.")}
&nbsp;
{_t("You can change this at any time from room settings.")}
</p>
);
}
let e2eeSection: JSX.Element;
@ -282,31 +293,35 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
microcopy = _t("Your server requires encryption to be enabled in private rooms.");
}
} else {
microcopy = _t("Your server admin has disabled end-to-end encryption by default " +
"in private rooms & Direct Messages.");
microcopy = _t(
"Your server admin has disabled end-to-end encryption by default " +
"in private rooms & Direct Messages.",
);
}
e2eeSection = <React.Fragment>
<LabelledToggleSwitch
label={_t("Enable end-to-end encryption")}
onChange={this.onEncryptedChange}
value={this.state.isEncrypted}
className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests
disabled={!this.state.canChangeEncryption}
/>
<p>{ microcopy }</p>
</React.Fragment>;
e2eeSection = (
<React.Fragment>
<LabelledToggleSwitch
label={_t("Enable end-to-end encryption")}
onChange={this.onEncryptedChange}
value={this.state.isEncrypted}
className="mx_CreateRoomDialog_e2eSwitch" // for end-to-end tests
disabled={!this.state.canChangeEncryption}
/>
<p>{microcopy}</p>
</React.Fragment>
);
}
let federateLabel = _t(
"You might enable this if the room will only be used for collaborating with internal " +
"teams on your homeserver. This cannot be changed later.",
"teams on your homeserver. This cannot be changed later.",
);
if (SdkConfig.get().default_federate === false) {
// We only change the label if the default setting is different to avoid jarring text changes to the
// user. They will have read the implications of turning this off/on, so no need to rephrase for them.
federateLabel = _t(
"You might disable this if the room will be used for collaborating with external " +
"teams who have their own homeserver. This cannot be changed later.",
"teams who have their own homeserver. This cannot be changed later.",
);
}
@ -316,7 +331,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
} else if (this.props.parentSpace) {
title = _t("Create a room");
} else {
title = this.state.joinRule === JoinRule.Public ? _t('Create a public room') : _t('Create a private room');
title = this.state.joinRule === JoinRule.Public ? _t("Create a public room") : _t("Create a private room");
}
return (
@ -330,14 +345,14 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
<div className="mx_Dialog_content">
<Field
ref={this.nameField}
label={_t('Name')}
label={_t("Name")}
onChange={this.onNameChange}
onValidate={this.onNameValidate}
value={this.state.name}
className="mx_CreateRoomDialog_name"
/>
<Field
label={_t('Topic (optional)')}
label={_t("Topic (optional)")}
onChange={this.onTopicChange}
value={this.state.topic}
className="mx_CreateRoomDialog_topic"
@ -352,28 +367,29 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
onChange={this.onJoinRuleChange}
/>
{ publicPrivateLabel }
{ e2eeSection }
{ aliasField }
{publicPrivateLabel}
{e2eeSection}
{aliasField}
<details onToggle={this.onDetailsToggled} className="mx_CreateRoomDialog_details">
<summary className="mx_CreateRoomDialog_details_summary">
{ this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') }
{this.state.detailsOpen ? _t("Hide advanced") : _t("Show advanced")}
</summary>
<LabelledToggleSwitch
label={_t(
"Block anyone not part of %(serverName)s from ever joining this room.",
{ serverName: MatrixClientPeg.getHomeserverName() },
)}
label={_t("Block anyone not part of %(serverName)s from ever joining this room.", {
serverName: MatrixClientPeg.getHomeserverName(),
})}
onChange={this.onNoFederateChange}
value={this.state.noFederate}
/>
<p>{ federateLabel }</p>
<p>{federateLabel}</p>
</details>
</div>
</form>
<DialogButtons primaryButton={isVideoRoom ? _t('Create video room') : _t('Create room')}
<DialogButtons
primaryButton={isVideoRoom ? _t("Create video room") : _t("Create room")}
onPrimaryButtonClick={this.onOk}
onCancel={this.onCancel} />
onCancel={this.onCancel}
/>
</BaseDialog>
);
}

View file

@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
import AccessibleButton from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
@ -85,99 +85,106 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
let joinRuleMicrocopy: JSX.Element;
if (joinRule === JoinRule.Restricted) {
joinRuleMicrocopy = <p>
{ _t(
"Anyone in <SpaceName/> will be able to find and join.", {}, {
SpaceName: () => <b>{ parentSpace.name }</b>,
},
) }
</p>;
joinRuleMicrocopy = (
<p>
{_t(
"Anyone in <SpaceName/> will be able to find and join.",
{},
{
SpaceName: () => <b>{parentSpace.name}</b>,
},
)}
</p>
);
} else if (joinRule === JoinRule.Public) {
joinRuleMicrocopy = <p>
{ _t(
"Anyone will be able to find and join this space, not just members of <SpaceName/>.", {}, {
SpaceName: () => <b>{ parentSpace.name }</b>,
},
) }
</p>;
joinRuleMicrocopy = (
<p>
{_t(
"Anyone will be able to find and join this space, not just members of <SpaceName/>.",
{},
{
SpaceName: () => <b>{parentSpace.name}</b>,
},
)}
</p>
);
} else if (joinRule === JoinRule.Invite) {
joinRuleMicrocopy = <p>
{ _t("Only people invited will be able to find and join this space.") }
</p>;
joinRuleMicrocopy = <p>{_t("Only people invited will be able to find and join this space.")}</p>;
}
return <BaseDialog
title={(
<SubspaceSelector
title={_t("Create a space")}
space={space}
value={parentSpace}
onChange={setParentSpace}
/>
)}
className="mx_CreateSubspaceDialog"
contentId="mx_CreateSubspaceDialog"
onFinished={onFinished}
fixedWidth={false}
>
<MatrixClientContext.Provider value={space.client}>
<div className="mx_CreateSubspaceDialog_content">
<div className="mx_CreateSubspaceDialog_betaNotice">
<BetaPill />
{ _t("Add a space to a space you manage.") }
return (
<BaseDialog
title={
<SubspaceSelector
title={_t("Create a space")}
space={space}
value={parentSpace}
onChange={setParentSpace}
/>
}
className="mx_CreateSubspaceDialog"
contentId="mx_CreateSubspaceDialog"
onFinished={onFinished}
fixedWidth={false}
>
<MatrixClientContext.Provider value={space.client}>
<div className="mx_CreateSubspaceDialog_content">
<div className="mx_CreateSubspaceDialog_betaNotice">
<BetaPill />
{_t("Add a space to a space you manage.")}
</div>
<SpaceCreateForm
busy={busy}
onSubmit={onCreateSubspaceClick}
setAvatar={setAvatar}
name={name}
setName={setName}
nameFieldRef={spaceNameField}
topic={topic}
setTopic={setTopic}
alias={alias}
setAlias={setAlias}
showAliasField={joinRule === JoinRule.Public}
aliasFieldRef={spaceAliasField}
>
<JoinRuleDropdown
label={_t("Space visibility")}
labelInvite={_t("Private space (invite only)")}
labelPublic={_t("Public space")}
labelRestricted={_t("Visible to space members")}
width={478}
value={joinRule}
onChange={setJoinRule}
/>
{joinRuleMicrocopy}
</SpaceCreateForm>
</div>
<SpaceCreateForm
busy={busy}
onSubmit={onCreateSubspaceClick}
setAvatar={setAvatar}
name={name}
setName={setName}
nameFieldRef={spaceNameField}
topic={topic}
setTopic={setTopic}
alias={alias}
setAlias={setAlias}
showAliasField={joinRule === JoinRule.Public}
aliasFieldRef={spaceAliasField}
>
<JoinRuleDropdown
label={_t("Space visibility")}
labelInvite={_t("Private space (invite only)")}
labelPublic={_t("Public space")}
labelRestricted={_t("Visible to space members")}
width={478}
value={joinRule}
onChange={setJoinRule}
/>
{ joinRuleMicrocopy }
</SpaceCreateForm>
</div>
<div className="mx_CreateSubspaceDialog_footer">
<div className="mx_CreateSubspaceDialog_footer_prompt">
<div>{_t("Want to add an existing space instead?")}</div>
<AccessibleButton
kind="link"
onClick={() => {
onAddExistingSpaceClick();
onFinished();
}}
>
{_t("Add existing space")}
</AccessibleButton>
</div>
<div className="mx_CreateSubspaceDialog_footer">
<div className="mx_CreateSubspaceDialog_footer_prompt">
<div>{ _t("Want to add an existing space instead?") }</div>
<AccessibleButton
kind="link"
onClick={() => {
onAddExistingSpaceClick();
onFinished();
}}
>
{ _t("Add existing space") }
<AccessibleButton kind="primary_outline" disabled={busy} onClick={() => onFinished(false)}>
{_t("Cancel")}
</AccessibleButton>
<AccessibleButton kind="primary" disabled={busy} onClick={onCreateSubspaceClick}>
{busy ? _t("Adding...") : _t("Add")}
</AccessibleButton>
</div>
<AccessibleButton kind="primary_outline" disabled={busy} onClick={() => onFinished(false)}>
{ _t("Cancel") }
</AccessibleButton>
<AccessibleButton kind="primary" disabled={busy} onClick={onCreateSubspaceClick}>
{ busy ? _t("Adding...") : _t("Add") }
</AccessibleButton>
</div>
</MatrixClientContext.Provider>
</BaseDialog>;
</MatrixClientContext.Provider>
</BaseDialog>
);
};
export default CreateSubspaceDialog;

View file

@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import dis from "../../../dispatcher/dispatcher";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import Modal from "../../../Modal";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import QuestionDialog from "./QuestionDialog";
@ -36,47 +36,48 @@ const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
title: _t("Sign out"),
description: _t(
"To avoid losing your chat history, you must export your room keys " +
"before logging out. You will need to go back to the newer version of " +
"%(brand)s to do this",
"before logging out. You will need to go back to the newer version of " +
"%(brand)s to do this",
{ brand },
),
button: _t("Sign out"),
focus: false,
onFinished: (doLogout) => {
if (doLogout) {
dis.dispatch({ action: 'logout' });
dis.dispatch({ action: "logout" });
props.onFinished(true);
}
},
});
};
const description =
_t(
"You've previously used a newer version of %(brand)s with this session. " +
const description = _t(
"You've previously used a newer version of %(brand)s with this session. " +
"To use this version again with end to end encryption, you will " +
"need to sign out and back in again.",
{ brand },
);
{ brand },
);
return (<BaseDialog className="mx_CryptoStoreTooNewDialog"
contentId='mx_Dialog_content'
title={_t("Incompatible Database")}
hasCancel={false}
onFinished={props.onFinished}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ description }
</div>
<DialogButtons primaryButton={_t('Continue With Encryption Disabled')}
return (
<BaseDialog
className="mx_CryptoStoreTooNewDialog"
contentId="mx_Dialog_content"
title={_t("Incompatible Database")}
hasCancel={false}
onPrimaryButtonClick={props.onFinished}
onFinished={props.onFinished}
>
<button onClick={_onLogoutClicked}>
{ _t('Sign out') }
</button>
</DialogButtons>
</BaseDialog>);
<div className="mx_Dialog_content" id="mx_Dialog_content">
{description}
</div>
<DialogButtons
primaryButton={_t("Continue With Encryption Disabled")}
hasCancel={false}
onPrimaryButtonClick={props.onFinished}
>
<button onClick={_onLogoutClicked}>{_t("Sign out")}</button>
</DialogButtons>
</BaseDialog>
);
};
export default CryptoStoreTooNewDialog;

View file

@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { AuthType, IAuthData } from 'matrix-js-sdk/src/interactive-auth';
import React from "react";
import { AuthType, IAuthData } from "matrix-js-sdk/src/interactive-auth";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from "../../../languageHandler";
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
import { DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
import StyledCheckbox from "../elements/StyledCheckbox";
@ -62,7 +62,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
continueKind: null,
};
this.initAuth(/* shouldErase= */false);
this.initAuth(/* shouldErase= */ false);
}
private onStagePhaseChange = (stage: AuthType, phase: string): void => {
@ -119,14 +119,17 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
// XXX: this should be returning a promise to maintain the state inside the state machine correct
// but given that a deactivation is followed by a local logout and all object instances being thrown away
// this isn't done.
MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => {
// Deactivation worked - logout & close this dialog
defaultDispatcher.fire(Action.TriggerLogout);
this.props.onFinished(true);
}).catch(e => {
logger.error(e);
this.setState({ errStr: _t("There was a problem communicating with the server. Please try again.") });
});
MatrixClientPeg.get()
.deactivateAccount(auth, this.state.shouldErase)
.then((r) => {
// Deactivation worked - logout & close this dialog
defaultDispatcher.fire(Action.TriggerLogout);
this.props.onFinished(true);
})
.catch((e) => {
logger.error(e);
this.setState({ errStr: _t("There was a problem communicating with the server. Please try again.") });
});
};
private onEraseFieldChange = (ev: React.FormEvent<HTMLInputElement>): void => {
@ -141,7 +144,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
});
// As mentioned above, set up for auth again to get updated UIA session info
this.initAuth(/* shouldErase= */ev.currentTarget.checked);
this.initAuth(/* shouldErase= */ ev.currentTarget.checked);
};
private onCancel(): void {
@ -149,36 +152,37 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
}
private initAuth(shouldErase: boolean): void {
MatrixClientPeg.get().deactivateAccount(null, shouldErase).then(r => {
// If we got here, oops. The server didn't require any auth.
// Our application lifecycle will catch the error and do the logout bits.
// We'll try to log something in an vain attempt to record what happened (storage
// is also obliterated on logout).
logger.warn("User's account got deactivated without confirmation: Server had no auth");
this.setState({ errStr: _t("Server did not require any authentication") });
}).catch(e => {
if (e && e.httpStatus === 401 && e.data) {
// Valid UIA response
this.setState({ authData: e.data, authEnabled: true });
} else {
this.setState({ errStr: _t("Server did not return valid authentication information.") });
}
});
MatrixClientPeg.get()
.deactivateAccount(null, shouldErase)
.then((r) => {
// If we got here, oops. The server didn't require any auth.
// Our application lifecycle will catch the error and do the logout bits.
// We'll try to log something in an vain attempt to record what happened (storage
// is also obliterated on logout).
logger.warn("User's account got deactivated without confirmation: Server had no auth");
this.setState({ errStr: _t("Server did not require any authentication") });
})
.catch((e) => {
if (e && e.httpStatus === 401 && e.data) {
// Valid UIA response
this.setState({ authData: e.data, authEnabled: true });
} else {
this.setState({ errStr: _t("Server did not return valid authentication information.") });
}
});
}
public render() {
let error = null;
if (this.state.errStr) {
error = <div className="error">
{ this.state.errStr }
</div>;
error = <div className="error">{this.state.errStr}</div>;
}
let auth = <div>{ _t("Loading...") }</div>;
let auth = <div>{_t("Loading...")}</div>;
if (this.state.authData && this.state.authEnabled) {
auth = (
<div>
{ this.state.bodyText }
{this.state.bodyText}
<InteractiveAuth
matrixClient={MatrixClientPeg.get()}
authData={this.state.authData}
@ -204,27 +208,36 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
screenName="DeactivateAccount"
>
<div className="mx_Dialog_content">
<p>{ _t("Confirm that you would like to deactivate your account. If you proceed:") }</p>
<p>{_t("Confirm that you would like to deactivate your account. If you proceed:")}</p>
<ul>
<li>{ _t("You will not be able to reactivate your account") }</li>
<li>{ _t("You will no longer be able to log in") }</li>
<li>{ _t("No one will be able to reuse your username (MXID), including you: this username will remain unavailable") }</li>
<li>{ _t("You will leave all rooms and DMs that you are in") }</li>
<li>{ _t("You will be removed from the identity server: your friends will no longer be able to find you with your email or phone number") }</li>
<li>{_t("You will not be able to reactivate your account")}</li>
<li>{_t("You will no longer be able to log in")}</li>
<li>
{_t(
"No one will be able to reuse your username (MXID), including you: this username will remain unavailable",
)}
</li>
<li>{_t("You will leave all rooms and DMs that you are in")}</li>
<li>
{_t(
"You will be removed from the identity server: your friends will no longer be able to find you with your email or phone number",
)}
</li>
</ul>
<p>{ _t("Your old messages will still be visible to people who received them, just like emails you sent in the past. Would you like to hide your sent messages from people who join rooms in the future?") }</p>
<p>
{_t(
"Your old messages will still be visible to people who received them, just like emails you sent in the past. Would you like to hide your sent messages from people who join rooms in the future?",
)}
</p>
<div className="mx_DeactivateAccountDialog_input_section">
<p>
<StyledCheckbox
checked={this.state.shouldErase}
onChange={this.onEraseFieldChange}
>
{ _t("Hide my messages from new joiners") }
<StyledCheckbox checked={this.state.shouldErase} onChange={this.onEraseFieldChange}>
{_t("Hide my messages from new joiners")}
</StyledCheckbox>
</p>
{ error }
{ auth }
{error}
{auth}
</div>
</div>
</BaseDialog>

View file

@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useState } from 'react';
import React, { useState } from "react";
import { _t, _td } from '../../../languageHandler';
import { _t, _td } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import BaseDialog from "./BaseDialog";
import { TimelineEventEditor } from "./devtools/Event";
@ -26,11 +26,11 @@ import VerificationExplorer from "./devtools/VerificationExplorer";
import SettingExplorer from "./devtools/SettingExplorer";
import { RoomStateExplorer } from "./devtools/RoomState";
import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./devtools/BaseTool";
import WidgetExplorer from './devtools/WidgetExplorer';
import WidgetExplorer from "./devtools/WidgetExplorer";
import { AccountDataExplorer, RoomAccountDataExplorer } from "./devtools/AccountData";
import SettingsFlag from "../elements/SettingsFlag";
import { SettingLevel } from "../../../settings/SettingLevel";
import ServerInfo from './devtools/ServerInfo';
import ServerInfo from "./devtools/ServerInfo";
enum Category {
Room,
@ -83,41 +83,47 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => {
const onBack = () => {
onFinished(false);
};
body = <BaseTool onBack={onBack}>
{ Object.entries(Tools).map(([category, tools]) => (
<div key={category}>
<h3>{ _t(categoryLabels[category]) }</h3>
{ tools.map(([label, tool]) => {
const onClick = () => {
setTool([label, tool]);
};
return <button className="mx_DevTools_button" key={label} onClick={onClick}>
{ _t(label) }
</button>;
}) }
body = (
<BaseTool onBack={onBack}>
{Object.entries(Tools).map(([category, tools]) => (
<div key={category}>
<h3>{_t(categoryLabels[category])}</h3>
{tools.map(([label, tool]) => {
const onClick = () => {
setTool([label, tool]);
};
return (
<button className="mx_DevTools_button" key={label} onClick={onClick}>
{_t(label)}
</button>
);
})}
</div>
))}
<div>
<h3>{_t("Options")}</h3>
<SettingsFlag name="developerMode" level={SettingLevel.ACCOUNT} />
<SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} />
<SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} />
</div>
)) }
<div>
<h3>{ _t("Options") }</h3>
<SettingsFlag name="developerMode" level={SettingLevel.ACCOUNT} />
<SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} />
<SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} />
</div>
</BaseTool>;
</BaseTool>
);
}
const label = tool ? tool[0] : _t("Toolbox");
return (
<BaseDialog className="mx_QuestionDialog" onFinished={onFinished} title={_t("Developer Tools")}>
<MatrixClientContext.Consumer>
{ (cli) => <>
<div className="mx_DevTools_label_left">{ label }</div>
<div className="mx_DevTools_label_right">{ _t("Room ID: %(roomId)s", { roomId }) }</div>
<div className="mx_DevTools_label_bottom" />
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId) }}>
{ body }
</DevtoolsContext.Provider>
</> }
{(cli) => (
<>
<div className="mx_DevTools_label_left">{label}</div>
<div className="mx_DevTools_label_right">{_t("Room ID: %(roomId)s", { roomId })}</div>
<div className="mx_DevTools_label_bottom" />
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId) }}>
{body}
</DevtoolsContext.Provider>
</>
)}
</MatrixClientContext.Consumer>
</BaseDialog>
);

View file

@ -36,34 +36,25 @@ interface IProps extends IDialogProps {
export default class EndPollDialog extends React.Component<IProps> {
private onFinished = (endPoll: boolean) => {
const topAnswer = findTopAnswer(
this.props.event,
this.props.matrixClient,
this.props.getRelationsForEvent,
);
const topAnswer = findTopAnswer(this.props.event, this.props.matrixClient, this.props.getRelationsForEvent);
const message = (
(topAnswer === "")
const message =
topAnswer === ""
? _t("The poll has ended. No votes were cast.")
: _t(
"The poll has ended. Top answer: %(topAnswer)s",
{ topAnswer },
)
);
: _t("The poll has ended. Top answer: %(topAnswer)s", { topAnswer });
if (endPoll) {
const endEvent = PollEndEvent.from(this.props.event.getId(), message).serialize();
this.props.matrixClient.sendEvent(
this.props.event.getRoomId(), endEvent.type, endEvent.content,
).catch((e: any) => {
console.error("Failed to submit poll response event:", e);
Modal.createDialog(ErrorDialog, {
title: _t("Failed to end poll"),
description: _t(
"Sorry, the poll did not end. Please try again."),
this.props.matrixClient
.sendEvent(this.props.event.getRoomId(), endEvent.type, endEvent.content)
.catch((e: any) => {
console.error("Failed to submit poll response event:", e);
Modal.createDialog(ErrorDialog, {
title: _t("Failed to end poll"),
description: _t("Sorry, the poll did not end. Please try again."),
});
});
});
}
this.props.onFinished(endPoll);
};
@ -72,13 +63,11 @@ export default class EndPollDialog extends React.Component<IProps> {
return (
<QuestionDialog
title={_t("End Poll")}
description={
_t(
"Are you sure you want to end this poll? " +
description={_t(
"Are you sure you want to end this poll? " +
"This will show the final results of the poll and " +
"stop people from being able to vote.",
)
}
)}
button={_t("End Poll")}
onFinished={(endPoll: boolean) => this.onFinished(endPoll)}
/>

View file

@ -25,9 +25,9 @@ limitations under the License.
* });
*/
import React from 'react';
import React from "react";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
interface IProps {
@ -60,16 +60,16 @@ export default class ErrorDialog extends React.Component<IProps, IState> {
<BaseDialog
className="mx_ErrorDialog"
onFinished={this.props.onFinished}
title={this.props.title || _t('Error')}
title={this.props.title || _t("Error")}
headerImage={this.props.headerImage}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ this.props.description || _t('An error has occurred.') }
<div className="mx_Dialog_content" id="mx_Dialog_content">
{this.props.description || _t("An error has occurred.")}
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.onClick} autoFocus={this.props.focus}>
{ this.props.button || _t('OK') }
{this.props.button || _t("OK")}
</button>
</div>
</BaseDialog>

View file

@ -25,12 +25,7 @@ import DialogButtons from "../elements/DialogButtons";
import Field from "../elements/Field";
import StyledRadioGroup from "../elements/StyledRadioGroup";
import StyledCheckbox from "../elements/StyledCheckbox";
import {
ExportFormat,
ExportType,
textForFormat,
textForType,
} from "../../../utils/exportUtils/exportUtils";
import { ExportFormat, ExportType, textForFormat, textForType } from "../../../utils/exportUtils/exportUtils";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import HTMLExporter from "../../../utils/exportUtils/HtmlExport";
import JSONExporter from "../../../utils/exportUtils/JSONExport";
@ -69,8 +64,7 @@ const useExportFormState = (): ExportConfig => {
const [exportFormat, setExportFormat] = useState(config.format ?? ExportFormat.Html);
const [exportType, setExportType] = useState(config.range ?? ExportType.Timeline);
const [includeAttachments, setAttachments] =
useState(config.includeAttachments ?? false);
const [includeAttachments, setAttachments] = useState(config.includeAttachments ?? false);
const [numberOfMessages, setNumberOfMessages] = useState<number>(config.numberOfMessages ?? 100);
const [sizeLimit, setSizeLimit] = useState<number | null>(config.sizeMb ?? 8);
@ -109,14 +103,11 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
const [displayCancel, setCancelWarning] = useState(false);
const [exportCancelled, setExportCancelled] = useState(false);
const [exportSuccessful, setExportSuccessful] = useState(false);
const [exporter, setExporter] = useStateCallback<Exporter>(
null,
async (exporter: Exporter) => {
await exporter?.export().then(() => {
if (!exportCancelled) setExportSuccessful(true);
});
},
);
const [exporter, setExporter] = useStateCallback<Exporter>(null, async (exporter: Exporter) => {
await exporter?.export().then(() => {
if (!exportCancelled) setExportSuccessful(true);
});
});
const startExport = async () => {
const exportOptions = {
@ -126,34 +117,13 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
};
switch (exportFormat) {
case ExportFormat.Html:
setExporter(
new HTMLExporter(
room,
ExportType[exportType],
exportOptions,
setExportProgressText,
),
);
setExporter(new HTMLExporter(room, ExportType[exportType], exportOptions, setExportProgressText));
break;
case ExportFormat.Json:
setExporter(
new JSONExporter(
room,
ExportType[exportType],
exportOptions,
setExportProgressText,
),
);
setExporter(new JSONExporter(room, ExportType[exportType], exportOptions, setExportProgressText));
break;
case ExportFormat.PlainText:
setExporter(
new PlainTextExporter(
room,
ExportType[exportType],
exportOptions,
setExportProgressText,
),
);
setExporter(new PlainTextExporter(room, ExportType[exportType], exportOptions, setExportProgressText));
break;
default:
logger.error("Unknown export format");
@ -162,17 +132,18 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
};
const onExportClick = async () => {
const isValidSize = !setSizeLimit || (await sizeLimitRef.current.validate({
focused: false,
}));
const isValidSize =
!setSizeLimit ||
(await sizeLimitRef.current.validate({
focused: false,
}));
if (!isValidSize) {
sizeLimitRef.current.validate({ focused: true });
return;
}
if (exportType === ExportType.LastNMessages) {
const isValidNumberOfMessages =
await messageCountRef.current.validate({ focused: false });
const isValidNumberOfMessages = await messageCountRef.current.validate({ focused: false });
if (!isValidNumberOfMessages) {
messageCountRef.current.validate({ focused: true });
return;
@ -197,7 +168,8 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
max,
});
},
}, {
},
{
key: "number",
test: ({ value }) => {
const parsedSize = parseInt(value, 10);
@ -206,10 +178,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
invalid: () => {
const min = 1;
const max = 2000;
return _t(
"Size can only be a number between %(min)s MB and %(max)s MB",
{ min, max },
);
return _t("Size can only be a number between %(min)s MB and %(max)s MB", { min, max });
},
},
],
@ -235,7 +204,8 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
max,
});
},
}, {
},
{
key: "number",
test: ({ value }) => {
const parsedSize = parseInt(value, 10);
@ -244,10 +214,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
invalid: () => {
const min = 1;
const max = 10 ** 8;
return _t(
"Number of messages can only be a number between %(min)s and %(max)s",
{ min, max },
);
return _t("Number of messages can only be a number between %(min)s and %(max)s", { min, max });
},
},
],
@ -278,7 +245,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
const exportTypeOptions = Object.keys(ExportType).map((type) => {
return (
<option key={type} value={ExportType[type]}>
{ textForType(ExportType[type]) }
{textForType(ExportType[type])}
</option>
);
});
@ -301,7 +268,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
);
}
const sizePostFix = <span>{ _t("MB") }</span>;
const sizePostFix = <span>{_t("MB")}</span>;
if (exportCancelled) {
// Display successful cancellation message
@ -318,9 +285,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
return (
<InfoDialog
title={_t("Export Successful")}
description={_t(
"Your export was successful. Find it in your Downloads folder.",
)}
description={_t("Your export was successful. Find it in your Downloads folder.")}
hasCloseButton={true}
onFinished={onFinished}
/>
@ -335,11 +300,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
onFinished={onFinished}
fixedWidth={true}
>
<p>
{ _t(
"Are you sure you want to stop exporting your data? If you do, you'll need to start over.",
) }
</p>
<p>{_t("Are you sure you want to stop exporting your data? If you do, you'll need to start over.")}</p>
<DialogButtons
primaryButton={_t("Stop")}
primaryButtonClass="danger"
@ -361,32 +322,25 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
onFinished={onFinished}
fixedWidth={true}
>
{ !isExporting ? <p>
{ _t(
"Select from the options below to export chats from your timeline",
) }
</p> : null }
{!isExporting ? <p>{_t("Select from the options below to export chats from your timeline")}</p> : null}
<div className="mx_ExportDialog_options">
{ !!setExportFormat && <>
<span className="mx_ExportDialog_subheading">
{ _t("Format") }
</span>
{!!setExportFormat && (
<>
<span className="mx_ExportDialog_subheading">{_t("Format")}</span>
<StyledRadioGroup
name="exportFormat"
value={exportFormat}
onChange={(key) => setExportFormat(ExportFormat[key])}
definitions={exportFormatOptions}
/>
</> }
<StyledRadioGroup
name="exportFormat"
value={exportFormat}
onChange={(key) => setExportFormat(ExportFormat[key])}
definitions={exportFormatOptions}
/>
</>
)}
{
!!setExportType && <>
<span className="mx_ExportDialog_subheading">
{ _t("Messages") }
</span>
{!!setExportType && (
<>
<span className="mx_ExportDialog_subheading">{_t("Messages")}</span>
<Field
id="export-type"
@ -396,52 +350,47 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
setExportType(ExportType[e.target.value]);
}}
>
{ exportTypeOptions }
{exportTypeOptions}
</Field>
{ messageCount }
{messageCount}
</>
}
)}
{ setSizeLimit && <>
<span className="mx_ExportDialog_subheading">
{ _t("Size Limit") }
</span>
{setSizeLimit && (
<>
<span className="mx_ExportDialog_subheading">{_t("Size Limit")}</span>
<Field
id="size-limit"
type="number"
autoComplete="off"
onValidate={onValidateSize}
element="input"
ref={sizeLimitRef}
value={sizeLimit.toString()}
postfixComponent={sizePostFix}
onChange={(e) => setSizeLimit(parseInt(e.target.value))}
/>
</> }
{ setAttachments && <>
<StyledCheckbox
className="mx_ExportDialog_attachments-checkbox"
id="include-attachments"
checked={includeAttachments}
onChange={(e) =>
setAttachments(
(e.target as HTMLInputElement).checked,
)
}
>
{ _t("Include Attachments") }
</StyledCheckbox>
</> }
<Field
id="size-limit"
type="number"
autoComplete="off"
onValidate={onValidateSize}
element="input"
ref={sizeLimitRef}
value={sizeLimit.toString()}
postfixComponent={sizePostFix}
onChange={(e) => setSizeLimit(parseInt(e.target.value))}
/>
</>
)}
{setAttachments && (
<>
<StyledCheckbox
className="mx_ExportDialog_attachments-checkbox"
id="include-attachments"
checked={includeAttachments}
onChange={(e) => setAttachments((e.target as HTMLInputElement).checked)}
>
{_t("Include Attachments")}
</StyledCheckbox>
</>
)}
</div>
{ isExporting ? (
<div data-test-id='export-progress' className="mx_ExportDialog_progress">
{isExporting ? (
<div data-test-id="export-progress" className="mx_ExportDialog_progress">
<Spinner w={24} h={24} />
<p>
{ exportProgressText }
</p>
<p>{exportProgressText}</p>
<DialogButtons
primaryButton={_t("Cancel")}
primaryButtonClass="danger"
@ -455,7 +404,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
onPrimaryButtonClick={onExportClick}
onCancel={() => onFinished(false)}
/>
) }
)}
</BaseDialog>
);
}

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from "react";
import QuestionDialog from './QuestionDialog';
import { _t } from '../../../languageHandler';
import QuestionDialog from "./QuestionDialog";
import { _t } from "../../../languageHandler";
import Field from "../elements/Field";
import AccessibleButton from "../elements/AccessibleButton";
import SdkConfig from "../../../SdkConfig";
@ -29,8 +29,8 @@ import { submitFeedback } from "../../../rageshake/submit-rageshake";
import { useStateToggle } from "../../../hooks/useStateToggle";
import StyledCheckbox from "../elements/StyledCheckbox";
const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" +
"?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
const existingIssuesUrl =
"https://github.com/vector-im/element-web/issues" + "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new/choose";
interface IProps extends IDialogProps {
@ -62,8 +62,8 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
}
Modal.createDialog(InfoDialog, {
title: _t('Feedback sent'),
description: _t('Thank you!'),
title: _t("Feedback sent"),
description: _t("Thank you!"),
});
}
props.onFinished();
@ -71,73 +71,94 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
let feedbackSection;
if (rageshakeUrl) {
feedbackSection = <div className="mx_FeedbackDialog_section mx_FeedbackDialog_rateApp">
<h3>{ _t("Comment") }</h3>
feedbackSection = (
<div className="mx_FeedbackDialog_section mx_FeedbackDialog_rateApp">
<h3>{_t("Comment")}</h3>
<p>{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }</p>
<p>{_t("Your platform and username will be noted to help us use your feedback as much as we can.")}</p>
<Field
id="feedbackComment"
label={_t("Feedback")}
type="text"
autoComplete="off"
value={comment}
element="textarea"
onChange={(ev) => {
setComment(ev.target.value);
}}
ref={feedbackRef}
/>
<Field
id="feedbackComment"
label={_t("Feedback")}
type="text"
autoComplete="off"
value={comment}
element="textarea"
onChange={(ev) => {
setComment(ev.target.value);
}}
ref={feedbackRef}
/>
<StyledCheckbox
checked={canContact}
onChange={toggleCanContact}
>
{ _t("You may contact me if you want to follow up or to let me test out upcoming ideas") }
</StyledCheckbox>
</div>;
<StyledCheckbox checked={canContact} onChange={toggleCanContact}>
{_t("You may contact me if you want to follow up or to let me test out upcoming ideas")}
</StyledCheckbox>
</div>
);
}
let bugReports = null;
if (rageshakeUrl) {
bugReports = (
<p className="mx_FeedbackDialog_section_microcopy">{
_t("PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> " +
"to help us track down the problem.", {}, {
debugLogsLink: sub => (
<AccessibleButton kind="link_inline" onClick={onDebugLogsLinkClick}>{ sub }</AccessibleButton>
),
})
}</p>
<p className="mx_FeedbackDialog_section_microcopy">
{_t(
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> " +
"to help us track down the problem.",
{},
{
debugLogsLink: (sub) => (
<AccessibleButton kind="link_inline" onClick={onDebugLogsLinkClick}>
{sub}
</AccessibleButton>
),
},
)}
</p>
);
}
return (<QuestionDialog
className="mx_FeedbackDialog"
hasCancelButton={!!hasFeedback}
title={_t("Feedback")}
description={<React.Fragment>
<div className="mx_FeedbackDialog_section mx_FeedbackDialog_reportBug">
<h3>{ _t("Report a bug") }</h3>
<p>{
_t("Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. " +
"No match? <newIssueLink>Start a new one</newIssueLink>.", {}, {
existingIssuesLink: (sub) => {
return <a target="_blank" rel="noreferrer noopener" href={existingIssuesUrl}>{ sub }</a>;
},
newIssueLink: (sub) => {
return <a target="_blank" rel="noreferrer noopener" href={newIssueUrl}>{ sub }</a>;
},
})
}</p>
{ bugReports }
</div>
{ feedbackSection }
</React.Fragment>}
button={hasFeedback ? _t("Send feedback") : _t("Go back")}
buttonDisabled={hasFeedback && !comment}
onFinished={onFinished}
/>);
return (
<QuestionDialog
className="mx_FeedbackDialog"
hasCancelButton={!!hasFeedback}
title={_t("Feedback")}
description={
<React.Fragment>
<div className="mx_FeedbackDialog_section mx_FeedbackDialog_reportBug">
<h3>{_t("Report a bug")}</h3>
<p>
{_t(
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. " +
"No match? <newIssueLink>Start a new one</newIssueLink>.",
{},
{
existingIssuesLink: (sub) => {
return (
<a target="_blank" rel="noreferrer noopener" href={existingIssuesUrl}>
{sub}
</a>
);
},
newIssueLink: (sub) => {
return (
<a target="_blank" rel="noreferrer noopener" href={newIssueUrl}>
{sub}
</a>
);
},
},
)}
</p>
{bugReports}
</div>
{feedbackSection}
</React.Fragment>
}
button={hasFeedback ? _t("Send feedback") : _t("Go back")}
buttonDisabled={hasFeedback && !comment}
onFinished={onFinished}
/>
);
};
export default FeedbackDialog;

View file

@ -35,7 +35,7 @@ import { avatarUrlForUser } from "../../../Avatar";
import EventTile from "../rooms/EventTile";
import SearchBox from "../../structures/SearchBox";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { Alignment } from '../elements/Tooltip';
import { Alignment } from "../elements/Tooltip";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
@ -125,37 +125,37 @@ const Entry: React.FC<IEntryProps> = ({ room, type, content, matrixClient: cli,
className = "mx_ForwardList_sendFailed";
disabled = true;
title = _t("Failed to send");
icon = <NotificationBadge
notification={StaticNotificationState.RED_EXCLAMATION}
/>;
icon = <NotificationBadge notification={StaticNotificationState.RED_EXCLAMATION} />;
}
return <div className="mx_ForwardList_entry">
<AccessibleTooltipButton
className="mx_ForwardList_roomButton"
onClick={jumpToRoom}
title={_t("Open room")}
alignment={Alignment.Top}
>
<DecoratedRoomAvatar room={room} avatarSize={32} />
<span className="mx_ForwardList_entry_name">{ room.name }</span>
<RoomContextDetails component="span" className="mx_ForwardList_entry_detail" room={room} />
</AccessibleTooltipButton>
<AccessibleTooltipButton
kind={sendState === SendState.Failed ? "danger_outline" : "primary_outline"}
className={`mx_ForwardList_sendButton ${className}`}
onClick={send}
disabled={disabled}
title={title}
alignment={Alignment.Top}
>
<div className="mx_ForwardList_sendLabel">{ _t("Send") }</div>
{ icon }
</AccessibleTooltipButton>
</div>;
return (
<div className="mx_ForwardList_entry">
<AccessibleTooltipButton
className="mx_ForwardList_roomButton"
onClick={jumpToRoom}
title={_t("Open room")}
alignment={Alignment.Top}
>
<DecoratedRoomAvatar room={room} avatarSize={32} />
<span className="mx_ForwardList_entry_name">{room.name}</span>
<RoomContextDetails component="span" className="mx_ForwardList_entry_detail" room={room} />
</AccessibleTooltipButton>
<AccessibleTooltipButton
kind={sendState === SendState.Failed ? "danger_outline" : "primary_outline"}
className={`mx_ForwardList_sendButton ${className}`}
onClick={send}
disabled={disabled}
title={title}
alignment={Alignment.Top}
>
<div className="mx_ForwardList_sendLabel">{_t("Send")}</div>
{icon}
</AccessibleTooltipButton>
</div>
);
};
const transformEvent = (event: MatrixEvent): {type: string, content: IContent } => {
const transformEvent = (event: MatrixEvent): { type: string; content: IContent } => {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
"m.relates_to": _, // strip relations - in future we will attach a relation pointing at the original event
@ -197,7 +197,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
const userId = cli.getUserId();
const [profileInfo, setProfileInfo] = useState<any>({});
useEffect(() => {
cli.getProfileInfo(userId).then(info => setProfileInfo(info));
cli.getProfileInfo(userId).then((info) => setProfileInfo(info));
}, [cli, userId]);
const { type, content } = transformEvent(event);
@ -218,10 +218,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
rawDisplayName: profileInfo.displayname,
userId,
getAvatarUrl: (..._) => {
return avatarUrlForUser(
{ avatarUrl: profileInfo.avatar_url },
AVATAR_SIZE, AVATAR_SIZE, "crop",
);
return avatarUrlForUser({ avatarUrl: profileInfo.avatar_url }, AVATAR_SIZE, AVATAR_SIZE, "crop");
},
getMxcAvatarUrl: () => profileInfo.avatar_url,
} as RoomMember;
@ -231,16 +228,16 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
const previewLayout = useSettingValue<Layout>("layout");
let rooms = useMemo(() => sortRooms(
cli.getVisibleRooms().filter(
room => room.getMyMembership() === "join" && !room.isSpaceRoom(),
),
), [cli]);
let rooms = useMemo(
() =>
sortRooms(cli.getVisibleRooms().filter((room) => room.getMyMembership() === "join" && !room.isSpaceRoom())),
[cli],
);
if (lcQuery) {
rooms = new QueryMatcher<Room>(rooms, {
keys: ["name"],
funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
shouldMatchWordsOnly: false,
}).match(lcQuery);
}
@ -252,7 +249,12 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
<EntityTile
className="mx_EntityTile_ellipsis"
avatarJsx={
<BaseAvatar url={require("../../../../res/img/ellipsis.svg").default} name="..." width={36} height={36} />
<BaseAvatar
url={require("../../../../res/img/ellipsis.svg").default}
name="..."
width={36}
height={36}
/>
}
name={text}
presenceState="online"
@ -262,58 +264,61 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
);
}
return <BaseDialog
title={_t("Forward message")}
className="mx_ForwardDialog"
contentId="mx_ForwardList"
onFinished={onFinished}
fixedWidth={false}
>
<h3>{ _t("Message preview") }</h3>
<div className={classnames("mx_ForwardDialog_preview", {
"mx_IRCLayout": previewLayout == Layout.IRC,
})}>
<EventTile
mxEvent={mockEvent}
layout={previewLayout}
permalinkCreator={permalinkCreator}
as="div"
/>
</div>
<hr />
<div className="mx_ForwardList" id="mx_ForwardList">
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={_t("Search for rooms or people")}
onSearch={setQuery}
autoFocus={true}
/>
<AutoHideScrollbar className="mx_ForwardList_content">
{ rooms.length > 0 ? (
<div className="mx_ForwardList_results">
<TruncatedList
className="mx_ForwardList_resultsList"
truncateAt={truncateAt}
createOverflowElement={overflowTile}
getChildren={(start, end) => rooms.slice(start, end).map(room =>
<Entry
key={room.roomId}
room={room}
type={type}
content={content}
matrixClient={cli}
onFinished={onFinished}
/>,
)}
getChildCount={() => rooms.length}
/>
</div>
) : <span className="mx_ForwardList_noResults">
{ _t("No results") }
</span> }
</AutoHideScrollbar>
</div>
</BaseDialog>;
return (
<BaseDialog
title={_t("Forward message")}
className="mx_ForwardDialog"
contentId="mx_ForwardList"
onFinished={onFinished}
fixedWidth={false}
>
<h3>{_t("Message preview")}</h3>
<div
className={classnames("mx_ForwardDialog_preview", {
mx_IRCLayout: previewLayout == Layout.IRC,
})}
>
<EventTile mxEvent={mockEvent} layout={previewLayout} permalinkCreator={permalinkCreator} as="div" />
</div>
<hr />
<div className="mx_ForwardList" id="mx_ForwardList">
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={_t("Search for rooms or people")}
onSearch={setQuery}
autoFocus={true}
/>
<AutoHideScrollbar className="mx_ForwardList_content">
{rooms.length > 0 ? (
<div className="mx_ForwardList_results">
<TruncatedList
className="mx_ForwardList_resultsList"
truncateAt={truncateAt}
createOverflowElement={overflowTile}
getChildren={(start, end) =>
rooms
.slice(start, end)
.map((room) => (
<Entry
key={room.roomId}
room={room}
type={type}
content={content}
matrixClient={cli}
onFinished={onFinished}
/>
))
}
getChildCount={() => rooms.length}
/>
</div>
) : (
<span className="mx_ForwardList_noResults">{_t("No results")}</span>
)}
</AutoHideScrollbar>
</div>
</BaseDialog>
);
};
export default ForwardDialog;

View file

@ -16,8 +16,8 @@ limitations under the License.
import React, { useState } from "react";
import QuestionDialog from './QuestionDialog';
import { _t } from '../../../languageHandler';
import QuestionDialog from "./QuestionDialog";
import { _t } from "../../../languageHandler";
import Field from "../elements/Field";
import SdkConfig from "../../../SdkConfig";
import { IDialogProps } from "./IDialogProps";
@ -59,43 +59,47 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
});
};
return (<QuestionDialog
className="mx_GenericFeatureFeedbackDialog"
hasCancelButton={true}
title={title}
description={<React.Fragment>
<div className="mx_GenericFeatureFeedbackDialog_subheading">
{ subheading }
&nbsp;
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
&nbsp;
{ children }
</div>
return (
<QuestionDialog
className="mx_GenericFeatureFeedbackDialog"
hasCancelButton={true}
title={title}
description={
<React.Fragment>
<div className="mx_GenericFeatureFeedbackDialog_subheading">
{subheading}
&nbsp;
{_t("Your platform and username will be noted to help us use your feedback as much as we can.")}
&nbsp;
{children}
</div>
<Field
id="feedbackComment"
label={_t("Feedback")}
type="text"
autoComplete="off"
value={comment}
element="textarea"
onChange={(ev) => {
setComment(ev.target.value);
}}
autoFocus={true}
/>
<Field
id="feedbackComment"
label={_t("Feedback")}
type="text"
autoComplete="off"
value={comment}
element="textarea"
onChange={(ev) => {
setComment(ev.target.value);
}}
autoFocus={true}
/>
<StyledCheckbox
checked={canContact}
onChange={e => setCanContact((e.target as HTMLInputElement).checked)}
>
{ _t("You may contact me if you have any follow up questions") }
</StyledCheckbox>
</React.Fragment>}
button={_t("Send feedback")}
buttonDisabled={!comment}
onFinished={sendFeedback}
/>);
<StyledCheckbox
checked={canContact}
onChange={(e) => setCanContact((e.target as HTMLInputElement).checked)}
>
{_t("You may contact me if you have any follow up questions")}
</StyledCheckbox>
</React.Fragment>
}
button={_t("Send feedback")}
buttonDisabled={!comment}
onFinished={sendFeedback}
/>
);
};
export default GenericFeatureFeedbackDialog;

View file

@ -20,17 +20,13 @@ import { logger } from "matrix-js-sdk/src/logger";
import AccessibleButton from "../elements/AccessibleButton";
import Modal from "../../../Modal";
import QuestionDialog from './QuestionDialog';
import QuestionDialog from "./QuestionDialog";
import SdkConfig from "../../../SdkConfig";
import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { HostSignupStore } from "../../../stores/HostSignupStore";
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
import {
IPostmessage,
IPostmessageResponseData,
PostmessageAction,
} from "./HostSignupDialogTypes";
import { IPostmessage, IPostmessageResponseData, PostmessageAction } from "./HostSignupDialogTypes";
import { IConfigOptions } from "../../../IConfigOptions";
import { SnakedObject } from "../../../utils/SnakedObject";
@ -117,21 +113,18 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
// We're done, close
return this.closeDialog();
} else {
Modal.createDialog(
QuestionDialog,
{
title: _t("Confirm abort of host creation"),
description: _t(
"Are you sure you wish to abort creation of the host? The process cannot be continued.",
),
button: _t("Abort"),
onFinished: result => {
if (result) {
return this.closeDialog();
}
},
Modal.createDialog(QuestionDialog, {
title: _t("Confirm abort of host creation"),
description: _t(
"Are you sure you wish to abort creation of the host? The process cannot be continued.",
),
button: _t("Abort"),
onFinished: (result) => {
if (result) {
return this.closeDialog();
}
},
);
});
}
};
@ -177,44 +170,45 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
const textComponent = (
<>
<p>
{ _t("Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
"account to fetch verified email addresses. This data is not stored.", {
hostSignupBrand: this.config.get("brand"),
}) }
{_t(
"Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
"account to fetch verified email addresses. This data is not stored.",
{
hostSignupBrand: this.config.get("brand"),
},
)}
</p>
<p>
{ _t("Learn more in our <privacyPolicyLink />, <termsOfServiceLink /> and <cookiePolicyLink />.",
{_t(
"Learn more in our <privacyPolicyLink />, <termsOfServiceLink /> and <cookiePolicyLink />.",
{},
{
cookiePolicyLink: () => (
<a href={cookiePolicyUrl} target="_blank" rel="noreferrer noopener">
{ _t("Cookie Policy") }
{_t("Cookie Policy")}
</a>
),
privacyPolicyLink: () => (
<a href={privacyPolicyUrl} target="_blank" rel="noreferrer noopener">
{ _t("Privacy Policy") }
{_t("Privacy Policy")}
</a>
),
termsOfServiceLink: () => (
<a href={tosUrl} target="_blank" rel="noreferrer noopener">
{ _t("Terms of Service") }
{_t("Terms of Service")}
</a>
),
},
) }
)}
</p>
</>
);
Modal.createDialog(
QuestionDialog,
{
title: _t("You should know"),
description: textComponent,
button: _t("Continue"),
onFinished: this.onAccountDetailsDialogFinished,
},
);
Modal.createDialog(QuestionDialog, {
title: _t("You should know"),
description: textComponent,
button: _t("Continue"),
onFinished: this.onAccountDetailsDialogFinished,
});
};
public componentDidMount() {
@ -230,21 +224,19 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
public render(): React.ReactNode {
return (
<div className={classNames({ "mx_Dialog_wrapper": !this.state.minimized })}>
<div className={classNames({ mx_Dialog_wrapper: !this.state.minimized })}>
<div
className={classNames("mx_Dialog",
{
"mx_HostSignupDialog_minimized": this.state.minimized,
"mx_HostSignupDialog": !this.state.minimized,
},
)}
className={classNames("mx_Dialog", {
mx_HostSignupDialog_minimized: this.state.minimized,
mx_HostSignupDialog: !this.state.minimized,
})}
>
{ this.state.minimized &&
{this.state.minimized && (
<div className="mx_Dialog_header mx_Dialog_headerWithButton">
<div className="mx_Dialog_title">
{ _t("%(hostSignupBrand)s Setup", {
{_t("%(hostSignupBrand)s Setup", {
hostSignupBrand: this.config.get("brand"),
}) }
})}
</div>
<AccessibleButton
className="mx_HostSignup_maximize_button"
@ -253,8 +245,8 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
title={_t("Maximise dialog")}
/>
</div>
}
{ !this.state.minimized &&
)}
{!this.state.minimized && (
<div className="mx_Dialog_header mx_Dialog_headerWithCancel">
<AccessibleButton
onClick={this.minimizeDialog}
@ -269,25 +261,18 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
title={_t("Close dialog")}
/>
</div>
}
{ this.state.error &&
<div>
{ this.state.error }
</div>
}
{ !this.state.error &&
)}
{this.state.error && <div>{this.state.error}</div>}
{!this.state.error && (
<iframe
title={_t(
"Upgrade to %(hostSignupBrand)s",
{
hostSignupBrand: this.config.get("brand"),
},
)}
title={_t("Upgrade to %(hostSignupBrand)s", {
hostSignupBrand: this.config.get("brand"),
})}
src={this.config.get("url")}
ref={this.iframeRef}
sandbox="allow-forms allow-scripts allow-same-origin allow-popups"
/>
}
)}
</div>
</div>
);

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { IGeneratedSas, ISasEvent, SasEvent } from "matrix-js-sdk/src/crypto/verification/SAS";
import { VerificationBase, VerificationEvent } from "matrix-js-sdk/src/crypto/verification/Base";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from "../../../languageHandler";
import { mediaFromMxc } from "../../../customisations/Media";
import VerificationComplete from "../verification/VerificationComplete";
import VerificationCancelled from "../verification/VerificationCancelled";
@ -80,16 +80,14 @@ export default class IncomingSasDialog extends React.Component<IProps, IState> {
public componentWillUnmount(): void {
if (this.state.phase !== PHASE_CANCELLED && this.state.phase !== PHASE_VERIFIED) {
this.props.verifier.cancel(new Error('User cancel'));
this.props.verifier.cancel(new Error("User cancel"));
}
this.props.verifier.removeListener(SasEvent.ShowSas, this.onVerifierShowSas);
}
private async fetchOpponentProfile(): Promise<void> {
try {
const prof = await MatrixClientPeg.get().getProfileInfo(
this.props.verifier.userId,
);
const prof = await MatrixClientPeg.get().getProfileInfo(this.props.verifier.userId);
this.setState({
opponentProfile: prof,
});
@ -110,11 +108,14 @@ export default class IncomingSasDialog extends React.Component<IProps, IState> {
private onContinueClick = (): void => {
this.setState({ phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM });
this.props.verifier.verify().then(() => {
this.setState({ phase: PHASE_VERIFIED });
}).catch((e) => {
logger.log("Verification failed", e);
});
this.props.verifier
.verify()
.then(() => {
this.setState({ phase: PHASE_VERIFIED });
})
.catch((e) => {
logger.log("Verification failed", e);
});
};
private onVerifierShowSas = (e: ISasEvent): void => {
@ -148,66 +149,76 @@ export default class IncomingSasDialog extends React.Component<IProps, IState> {
let profile;
const oppProfile = this.state.opponentProfile;
if (oppProfile) {
const url = oppProfile.avatar_url
? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48)
: null;
profile = <div className="mx_IncomingSasDialog_opponentProfile">
<BaseAvatar
name={oppProfile.displayname}
idName={this.props.verifier.userId}
url={url}
width={48}
height={48}
resizeMethod='crop'
/>
<h2>{ oppProfile.displayname }</h2>
</div>;
const url = oppProfile.avatar_url ? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48) : null;
profile = (
<div className="mx_IncomingSasDialog_opponentProfile">
<BaseAvatar
name={oppProfile.displayname}
idName={this.props.verifier.userId}
url={url}
width={48}
height={48}
resizeMethod="crop"
/>
<h2>{oppProfile.displayname}</h2>
</div>
);
} else if (this.state.opponentProfileError) {
profile = <div>
<BaseAvatar
name={this.props.verifier.userId.slice(1)}
idName={this.props.verifier.userId}
width={48}
height={48}
/>
<h2>{ this.props.verifier.userId }</h2>
</div>;
profile = (
<div>
<BaseAvatar
name={this.props.verifier.userId.slice(1)}
idName={this.props.verifier.userId}
width={48}
height={48}
/>
<h2>{this.props.verifier.userId}</h2>
</div>
);
} else {
profile = <Spinner />;
}
const userDetailText = [
<p key="p1">{ _t(
"Verify this user to mark them as trusted. " +
"Trusting users gives you extra peace of mind when using " +
"end-to-end encrypted messages.",
) }</p>,
<p key="p2">{ _t(
// NB. Below wording adjusted to singular 'session' until we have
// cross-signing
"Verifying this user will mark their session as trusted, and " +
"also mark your session as trusted to them.",
) }</p>,
<p key="p1">
{_t(
"Verify this user to mark them as trusted. " +
"Trusting users gives you extra peace of mind when using " +
"end-to-end encrypted messages.",
)}
</p>,
<p key="p2">
{_t(
// NB. Below wording adjusted to singular 'session' until we have
// cross-signing
"Verifying this user will mark their session as trusted, and " +
"also mark your session as trusted to them.",
)}
</p>,
];
const selfDetailText = [
<p key="p1">{ _t(
"Verify this device to mark it as trusted. " +
"Trusting this device gives you and other users extra peace of mind when using " +
"end-to-end encrypted messages.",
) }</p>,
<p key="p2">{ _t(
"Verifying this device will mark it as trusted, and users who have verified with " +
"you will trust this device.",
) }</p>,
<p key="p1">
{_t(
"Verify this device to mark it as trusted. " +
"Trusting this device gives you and other users extra peace of mind when using " +
"end-to-end encrypted messages.",
)}
</p>,
<p key="p2">
{_t(
"Verifying this device will mark it as trusted, and users who have verified with " +
"you will trust this device.",
)}
</p>,
];
return (
<div>
{ profile }
{ isSelf ? selfDetailText : userDetailText }
{profile}
{isSelf ? selfDetailText : userDetailText}
<DialogButtons
primaryButton={_t('Continue')}
primaryButton={_t("Continue")}
hasCancel={true}
onPrimaryButtonClick={this.onContinueClick}
onCancel={this.onCancelClick}
@ -217,20 +228,22 @@ export default class IncomingSasDialog extends React.Component<IProps, IState> {
}
private renderPhaseShowSas(): JSX.Element {
return <VerificationShowSas
sas={this.showSasEvent.sas}
onCancel={this.onCancelClick}
onDone={this.onSasMatchesClick}
isSelf={this.props.verifier.userId === MatrixClientPeg.get().getUserId()}
inDialog={true}
/>;
return (
<VerificationShowSas
sas={this.showSasEvent.sas}
onCancel={this.onCancelClick}
onDone={this.onSasMatchesClick}
isSelf={this.props.verifier.userId === MatrixClientPeg.get().getUserId()}
inDialog={true}
/>
);
}
private renderPhaseWaitForPartnerToConfirm(): JSX.Element {
return (
<div>
<Spinner />
<p>{ _t("Waiting for partner to confirm...") }</p>
<p>{_t("Waiting for partner to confirm...")}</p>
</div>
);
}
@ -264,14 +277,9 @@ export default class IncomingSasDialog extends React.Component<IProps, IState> {
}
return (
<BaseDialog
title={_t("Incoming Verification Request")}
onFinished={this.onFinished}
fixedWidth={false}
>
{ body }
<BaseDialog title={_t("Incoming Verification Request")} onFinished={this.onFinished} fixedWidth={false}>
{body}
</BaseDialog>
);
}
}

View file

@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactNode, KeyboardEvent } from 'react';
import React, { ReactNode, KeyboardEvent } from "react";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
@ -35,8 +35,8 @@ interface IProps extends IDialogProps {
export default class InfoDialog extends React.Component<IProps> {
static defaultProps = {
title: '',
description: '',
title: "",
description: "",
hasCloseButton: false,
};
@ -50,18 +50,21 @@ export default class InfoDialog extends React.Component<IProps> {
className="mx_InfoDialog"
onFinished={this.props.onFinished}
title={this.props.title}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
hasCancel={this.props.hasCloseButton}
onKeyDown={this.props.onKeyDown}
fixedWidth={this.props.fixedWidth}
>
<div className={classNames("mx_Dialog_content", this.props.className)} id="mx_Dialog_content">
{ this.props.description }
{this.props.description}
</div>
{ this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')}
onPrimaryButtonClick={this.onFinished}
hasCancel={false}
/> }
{this.props.button !== false && (
<DialogButtons
primaryButton={this.props.button || _t("OK")}
onPrimaryButtonClick={this.onFinished}
hasCancel={false}
/>
)}
</BaseDialog>
);
}

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from "../../../languageHandler";
import dis from '../../../dispatcher/dispatcher';
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
@ -38,13 +38,13 @@ export default class IntegrationsDisabledDialog extends React.Component<IProps>
public render(): JSX.Element {
return (
<BaseDialog
className='mx_IntegrationsDisabledDialog'
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 className="mx_IntegrationsDisabledDialog_content">
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
</div>
<DialogButtons
primaryButton={_t("Settings")}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
@ -34,18 +34,18 @@ export default class IntegrationsImpossibleDialog extends React.Component<IProps
return (
<BaseDialog
className='mx_IntegrationsImpossibleDialog'
className="mx_IntegrationsImpossibleDialog"
hasCancel={false}
onFinished={this.props.onFinished}
title={_t("Integrations not allowed")}
>
<div className='mx_IntegrationsImpossibleDialog_content'>
<div className="mx_IntegrationsImpossibleDialog_content">
<p>
{ _t(
{_t(
"Your %(brand)s doesn't allow you to use an integration manager to do this. " +
"Please contact an admin.",
"Please contact an admin.",
{ brand },
) }
)}
</p>
</div>
<DialogButtons

View file

@ -16,12 +16,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { IAuthData } from "matrix-js-sdk/src/interactive-auth";
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
import BaseDialog from "./BaseDialog";
@ -144,7 +144,7 @@ export default class InteractiveAuthDialog extends React.Component<InteractiveAu
// Let's pick a title, body, and other params text that we'll show to the user. The order
// is most specific first, so stagePhase > our props > defaults.
let title = this.state.authError ? 'Error' : (this.props.title || _t('Authentication'));
let title = this.state.authError ? "Error" : this.props.title || _t("Authentication");
let body = this.state.authError ? null : this.props.body;
let continueText = null;
let continueKind = null;
@ -162,21 +162,18 @@ export default class InteractiveAuthDialog extends React.Component<InteractiveAu
let content;
if (this.state.authError) {
content = (
<div id='mx_Dialog_content'>
<div role="alert">{ this.state.authError.message || this.state.authError.toString() }</div>
<div id="mx_Dialog_content">
<div role="alert">{this.state.authError.message || this.state.authError.toString()}</div>
<br />
<AccessibleButton onClick={this.onDismissClick}
className="mx_GeneralButton"
autoFocus={true}
>
{ _t("Dismiss") }
<AccessibleButton onClick={this.onDismissClick} className="mx_GeneralButton" autoFocus={true}>
{_t("Dismiss")}
</AccessibleButton>
</div>
);
} else {
content = (
<div id='mx_Dialog_content'>
{ body }
<div id="mx_Dialog_content">
{body}
<InteractiveAuth
matrixClient={this.props.matrixClient}
authData={this.props.authData}
@ -191,12 +188,13 @@ export default class InteractiveAuthDialog extends React.Component<InteractiveAu
}
return (
<BaseDialog className="mx_InteractiveAuthDialog"
<BaseDialog
className="mx_InteractiveAuthDialog"
onFinished={this.props.onFinished}
title={title}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
>
{ content }
{content}
</BaseDialog>
);
}

File diff suppressed because it is too large Load diff

View file

@ -14,30 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useState, useCallback, useRef } from 'react';
import React, { useState, useCallback, useRef } from "react";
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import Spinner from "../elements/Spinner";
import { IDialogProps } from "./IDialogProps";
interface IProps extends IDialogProps {
failures: Record<string, Record<string, {
errcode: string;
error: string;
}>>;
failures: Record<
string,
Record<
string,
{
errcode: string;
error: string;
}
>
>;
source: string;
continuation: () => Promise<void>;
}
const KeySignatureUploadFailedDialog: React.FC<IProps> = ({
failures,
source,
continuation,
onFinished,
}) => {
const KeySignatureUploadFailedDialog: React.FC<IProps> = ({ failures, source, continuation, onFinished }) => {
const RETRIES = 2;
const [retry, setRetry] = useState(RETRIES);
const [cancelled, setCancelled] = useState(false);
@ -60,13 +61,10 @@ const KeySignatureUploadFailedDialog: React.FC<IProps> = ({
}).finally(() => {
setCancelled(true);
});
await Promise.race([
continuation(),
cancel,
]);
await Promise.race([continuation(), cancel]);
setSuccess(true);
} catch (e) {
setRetry(r => r-1);
setRetry((r) => r - 1);
} finally {
onCancel.current = onFinished;
setRetrying(false);
@ -78,44 +76,42 @@ const KeySignatureUploadFailedDialog: React.FC<IProps> = ({
const reason = causes.get(source) || defaultCause;
const brand = SdkConfig.get().brand;
body = (<div>
<p>{ _t("%(brand)s encountered an error during upload of:", { brand }) }</p>
<p>{ reason }</p>
{ retrying && <Spinner /> }
<pre>{ JSON.stringify(failures, null, 2) }</pre>
<DialogButtons
primaryButton='Retry'
hasCancel={true}
onPrimaryButtonClick={onRetry}
onCancel={onCancel.current}
primaryDisabled={retrying}
/>
</div>);
body = (
<div>
<p>{_t("%(brand)s encountered an error during upload of:", { brand })}</p>
<p>{reason}</p>
{retrying && <Spinner />}
<pre>{JSON.stringify(failures, null, 2)}</pre>
<DialogButtons
primaryButton="Retry"
hasCancel={true}
onPrimaryButtonClick={onRetry}
onCancel={onCancel.current}
primaryDisabled={retrying}
/>
</div>
);
} else {
let text = _t("Upload completed");
if (!success) {
text = cancelled ? _t("Cancelled signature upload") : _t("Unable to upload");
}
body = (<div>
<span>{ text }</span>
<DialogButtons
primaryButton={_t("OK")}
hasCancel={false}
onPrimaryButtonClick={onFinished}
/>
</div>);
body = (
<div>
<span>{text}</span>
<DialogButtons primaryButton={_t("OK")} hasCancel={false} onPrimaryButtonClick={onFinished} />
</div>
);
}
return (
<BaseDialog
title={success ?
_t("Signature upload success") :
_t("Signature upload failed")}
title={success ? _t("Signature upload success") : _t("Signature upload failed")}
fixedWidth={false}
onFinished={() => {}}
>
{ body }
{body}
</BaseDialog>
);
};

View file

@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import QuestionDialog from './QuestionDialog';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import QuestionDialog from "./QuestionDialog";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import { IDialogProps } from "./IDialogProps";
interface IProps extends IDialogProps {
@ -30,9 +30,9 @@ const LazyLoadingDisabledDialog: React.FC<IProps> = (props) => {
const brand = SdkConfig.get().brand;
const description1 = _t(
"You've previously used %(brand)s on %(host)s with lazy loading of members enabled. " +
"In this version lazy loading is disabled. " +
"As the local cache is not compatible between these two settings, " +
"%(brand)s needs to resync your account.",
"In this version lazy loading is disabled. " +
"As the local cache is not compatible between these two settings, " +
"%(brand)s needs to resync your account.",
{
brand,
host: props.host,
@ -40,20 +40,27 @@ const LazyLoadingDisabledDialog: React.FC<IProps> = (props) => {
);
const description2 = _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.",
"please close it as using %(brand)s on the same host with both " +
"lazy loading enabled and disabled simultaneously will cause issues.",
{
brand,
},
);
return (<QuestionDialog
hasCancelButton={false}
title={_t("Incompatible local cache")}
description={<div><p>{ description1 }</p><p>{ description2 }</p></div>}
button={_t("Clear cache and resync")}
onFinished={props.onFinished}
/>);
return (
<QuestionDialog
hasCancelButton={false}
title={_t("Incompatible local cache")}
description={
<div>
<p>{description1}</p>
<p>{description2}</p>
</div>
}
button={_t("Clear cache and resync")}
onFinished={props.onFinished}
/>
);
};
export default LazyLoadingDisabledDialog;

View file

@ -15,32 +15,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import QuestionDialog from './QuestionDialog';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import QuestionDialog from "./QuestionDialog";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import { IDialogProps } from "./IDialogProps";
interface IProps extends IDialogProps {}
const LazyLoadingResyncDialog: React.FC<IProps> = (props) => {
const brand = SdkConfig.get().brand;
const description =
_t(
"%(brand)s now uses 3-5x less memory, by only loading information " +
const description = _t(
"%(brand)s now uses 3-5x less memory, by only loading information " +
"about other users when needed. Please wait whilst we resynchronise " +
"with the server!",
{ brand },
);
{ brand },
);
return (<QuestionDialog
hasCancelButton={false}
title={_t("Updating %(brand)s", { brand })}
description={<div>{ description }</div>}
button={_t("OK")}
onFinished={props.onFinished}
/>);
return (
<QuestionDialog
hasCancelButton={false}
title={_t("Updating %(brand)s", { brand })}
description={<div>{description}</div>}
button={_t("OK")}
onFinished={props.onFinished}
/>
);
};
export default LazyLoadingResyncDialog;

View file

@ -18,7 +18,7 @@ import React, { useMemo, useState } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import DialogButtons from "../elements/DialogButtons";
import BaseDialog from "../dialogs/BaseDialog";
import SpaceStore from "../../../stores/spaces/SpaceStore";
@ -34,7 +34,7 @@ const isOnlyAdmin = (room: Room): boolean => {
if (room.getMember(userId).powerLevelNorm !== 100) {
return false; // user is not an admin
}
return room.getJoinedMembers().every(member => {
return room.getJoinedMembers().every((member) => {
// return true if every other member has a lower power level (we are highest)
return member.userId === userId || member.powerLevelNorm < 100;
});
@ -43,11 +43,17 @@ const isOnlyAdmin = (room: Room): boolean => {
const LeaveSpaceDialog: React.FC<IProps> = ({ space, onFinished }) => {
const spaceChildren = useMemo(() => {
const roomSet = new Set(SpaceStore.instance.getSpaceFilteredRoomIds(space.roomId));
SpaceStore.instance.traverseSpace(space.roomId, spaceId => {
if (space.roomId === spaceId) return; // skip the root node
roomSet.add(spaceId);
}, false);
return Array.from(roomSet).map(roomId => space.client.getRoom(roomId)).filter(Boolean);
SpaceStore.instance.traverseSpace(
space.roomId,
(spaceId) => {
if (space.roomId === spaceId) return; // skip the root node
roomSet.add(spaceId);
},
false,
);
return Array.from(roomSet)
.map((roomId) => space.client.getRoom(roomId))
.filter(Boolean);
}, [space]);
const [roomsToLeave, setRoomsToLeave] = useState<Room[]>([]);
const selectedRooms = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
@ -59,58 +65,65 @@ const LeaveSpaceDialog: React.FC<IProps> = ({ space, onFinished }) => {
let onlyAdminWarning;
if (isOnlyAdmin(space)) {
onlyAdminWarning = _t("You're the only admin of this space. " +
"Leaving it will mean no one has control over it.");
onlyAdminWarning = _t(
"You're the only admin of this space. " + "Leaving it will mean no one has control over it.",
);
} else {
const numChildrenOnlyAdminIn = roomsToLeave.filter(isOnlyAdmin).length;
if (numChildrenOnlyAdminIn > 0) {
onlyAdminWarning = _t("You're the only admin of some of the rooms or spaces you wish to leave. " +
"Leaving them will leave them without any admins.");
onlyAdminWarning = _t(
"You're the only admin of some of the rooms or spaces you wish to leave. " +
"Leaving them will leave them without any admins.",
);
}
}
return <BaseDialog
title={_t("Leave %(spaceName)s", { spaceName: space.name })}
className="mx_LeaveSpaceDialog"
contentId="mx_LeaveSpaceDialog"
onFinished={() => onFinished(false)}
fixedWidth={false}
>
<div className="mx_Dialog_content" id="mx_LeaveSpaceDialog">
<p>
{ _t("You are about to leave <spaceName/>.", {}, {
spaceName: () => <b>{ space.name }</b>,
}) }
&nbsp;
{ rejoinWarning }
{ rejoinWarning && (<>&nbsp;</>) }
{ spaceChildren.length > 0 && _t("Would you like to leave the rooms in this space?") }
</p>
return (
<BaseDialog
title={_t("Leave %(spaceName)s", { spaceName: space.name })}
className="mx_LeaveSpaceDialog"
contentId="mx_LeaveSpaceDialog"
onFinished={() => onFinished(false)}
fixedWidth={false}
>
<div className="mx_Dialog_content" id="mx_LeaveSpaceDialog">
<p>
{_t(
"You are about to leave <spaceName/>.",
{},
{
spaceName: () => <b>{space.name}</b>,
},
)}
&nbsp;
{rejoinWarning}
{rejoinWarning && <>&nbsp;</>}
{spaceChildren.length > 0 && _t("Would you like to leave the rooms in this space?")}
</p>
{ spaceChildren.length > 0 && (
<SpaceChildrenPicker
space={space}
spaceChildren={spaceChildren}
selected={selectedRooms}
onChange={setRoomsToLeave}
noneLabel={_t("Don't leave any rooms")}
allLabel={_t("Leave all rooms")}
specificLabel={_t("Leave some rooms")}
/>
) }
{spaceChildren.length > 0 && (
<SpaceChildrenPicker
space={space}
spaceChildren={spaceChildren}
selected={selectedRooms}
onChange={setRoomsToLeave}
noneLabel={_t("Don't leave any rooms")}
allLabel={_t("Leave all rooms")}
specificLabel={_t("Leave some rooms")}
/>
)}
{ onlyAdminWarning && <div className="mx_LeaveSpaceDialog_section_warning">
{ onlyAdminWarning }
</div> }
</div>
<DialogButtons
primaryButton={_t("Leave space")}
primaryButtonClass="danger"
onPrimaryButtonClick={() => onFinished(true, roomsToLeave)}
hasCancel={true}
onCancel={() => onFinished(false)}
/>
</BaseDialog>;
{onlyAdminWarning && <div className="mx_LeaveSpaceDialog_section_warning">{onlyAdminWarning}</div>}
</div>
<DialogButtons
primaryButton={_t("Leave space")}
primaryButtonClass="danger"
onPrimaryButtonClick={() => onFinished(true, roomsToLeave)}
hasCancel={true}
onCancel={() => onFinished(false)}
/>
</BaseDialog>
);
};
export default LeaveSpaceDialog;

View file

@ -15,15 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ComponentType } from 'react';
import React, { ComponentType } from "react";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { logger } from "matrix-js-sdk/src/logger";
import Modal from '../../../Modal';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog';
import Modal from "../../../Modal";
import dis from "../../../dispatcher/dispatcher";
import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import RestoreKeyBackupDialog from "./security/RestoreKeyBackupDialog";
import QuestionDialog from "./QuestionDialog";
import BaseDialog from "./BaseDialog";
import Spinner from "../elements/Spinner";
@ -42,7 +42,7 @@ interface IState {
export default class LogoutDialog extends React.Component<IProps, IState> {
static defaultProps = {
onFinished: function() {},
onFinished: function () {},
};
constructor(props) {
@ -81,9 +81,9 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
private onExportE2eKeysClicked = (): void => {
Modal.createDialogAsync(
import(
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
) as unknown as Promise<ComponentType<{}>>,
import("../../../async-components/views/dialogs/security/ExportE2eKeysDialog") as unknown as Promise<
ComponentType<{}>
>,
{
matrixClient: MatrixClientPeg.get(),
},
@ -92,7 +92,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
private onFinished = (confirmed: boolean): void => {
if (confirmed) {
dis.dispatch({ action: 'logout' });
dis.dispatch({ action: "logout" });
}
// close dialog
this.props.onFinished(confirmed);
@ -106,10 +106,13 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
Modal.createDialog(RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true);
} else {
Modal.createDialogAsync(
import(
"../../../async-components/views/dialogs/security/CreateKeyBackupDialog"
) as unknown as Promise<ComponentType<{}>>,
null, null, /* priority = */ false, /* static = */ true,
import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog") as unknown as Promise<
ComponentType<{}>
>,
null,
null,
/* priority = */ false,
/* static = */ true,
);
}
@ -118,7 +121,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
};
private onLogoutConfirm = (): void => {
dis.dispatch({ action: 'logout' });
dis.dispatch({ action: "logout" });
// close dialog
this.props.onFinished(true);
@ -126,19 +129,25 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
render() {
if (this.state.shouldLoadBackupStatus) {
const description = <div>
<p>{ _t(
"Encrypted messages are secured with end-to-end encryption. " +
"Only you and the recipient(s) have the keys to read these messages.",
) }</p>
<p>{ _t(
"When you sign out, these keys will be deleted from this device, " +
"which means you won't be able to read encrypted messages unless you " +
"have the keys for them on your other devices, or backed them up to the " +
"server.",
) }</p>
<p>{ _t("Back up your keys before signing out to avoid losing them.") }</p>
</div>;
const description = (
<div>
<p>
{_t(
"Encrypted messages are secured with end-to-end encryption. " +
"Only you and the recipient(s) have the keys to read these messages.",
)}
</p>
<p>
{_t(
"When you sign out, these keys will be deleted from this device, " +
"which means you won't be able to read encrypted messages unless you " +
"have the keys for them on your other devices, or backed them up to the " +
"server.",
)}
</p>
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
</div>
);
let dialogContent;
if (this.state.loading) {
@ -153,48 +162,51 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
setupButtonCaption = _t("Start using Key Backup");
}
dialogContent = <div>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ description }
dialogContent = (
<div>
<div className="mx_Dialog_content" id="mx_Dialog_content">
{description}
</div>
<DialogButtons
primaryButton={setupButtonCaption}
hasCancel={false}
onPrimaryButtonClick={this.onSetRecoveryMethodClick}
focus={true}
>
<button onClick={this.onLogoutConfirm}>{_t("I don't want my encrypted messages")}</button>
</DialogButtons>
<details>
<summary>{_t("Advanced")}</summary>
<p>
<button onClick={this.onExportE2eKeysClicked}>{_t("Manually export keys")}</button>
</p>
</details>
</div>
<DialogButtons primaryButton={setupButtonCaption}
hasCancel={false}
onPrimaryButtonClick={this.onSetRecoveryMethodClick}
focus={true}
>
<button onClick={this.onLogoutConfirm}>
{ _t("I don't want my encrypted messages") }
</button>
</DialogButtons>
<details>
<summary>{ _t("Advanced") }</summary>
<p><button onClick={this.onExportE2eKeysClicked}>
{ _t("Manually export keys") }
</button></p>
</details>
</div>;
);
}
// Not quite a standard question dialog as the primary button cancels
// the action and does something else instead, whilst non-default button
// confirms the action.
return (<BaseDialog
title={_t("You'll lose access to your encrypted messages")}
contentId='mx_Dialog_content'
hasCancel={true}
onFinished={this.onFinished}
>
{ dialogContent }
</BaseDialog>);
return (
<BaseDialog
title={_t("You'll lose access to your encrypted messages")}
contentId="mx_Dialog_content"
hasCancel={true}
onFinished={this.onFinished}
>
{dialogContent}
</BaseDialog>
);
} else {
return (<QuestionDialog
hasCancelButton={true}
title={_t("Sign out")}
description={_t(
"Are you sure you want to sign out?",
)}
button={_t("Sign out")}
onFinished={this.onFinished}
/>);
return (
<QuestionDialog
hasCancelButton={true}
title={_t("Sign out")}
description={_t("Are you sure you want to sign out?")}
button={_t("Sign out")}
onFinished={this.onFinished}
/>
);
}
}
}

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, { useMemo, useState } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import SearchBox from "../../structures/SearchBox";
@ -45,32 +45,37 @@ const Entry = ({ room, checked, onChange }) => {
}
}
return <label className="mx_ManageRestrictedJoinRuleDialog_entry">
<div>
return (
<label className="mx_ManageRestrictedJoinRuleDialog_entry">
<div>
{ localRoom
? <RoomAvatar room={room} height={20} width={20} />
: <RoomAvatar oobData={room} height={20} width={20} />
}
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{ room.name }</span>
<div>
{localRoom ? (
<RoomAvatar room={room} height={20} width={20} />
) : (
<RoomAvatar oobData={room} height={20} width={20} />
)}
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{room.name}</span>
</div>
{description && (
<div className="mx_ManageRestrictedJoinRuleDialog_entry_description">{description}</div>
)}
</div>
{ description && <div className="mx_ManageRestrictedJoinRuleDialog_entry_description">
{ description }
</div> }
</div>
<StyledCheckbox
onChange={onChange ? (e) => onChange(e.target.checked) : null}
checked={checked}
disabled={!onChange}
/>
</label>;
<StyledCheckbox
onChange={onChange ? (e) => onChange(e.target.checked) : null}
checked={checked}
disabled={!onChange}
/>
</label>
);
};
const addAllParents = (set: Set<Room>, room: Room): void => {
const cli = room.client;
const parents = Array.from(SpaceStore.instance.getKnownParents(room.roomId)).map(parentId => cli.getRoom(parentId));
const parents = Array.from(SpaceStore.instance.getKnownParents(room.roomId)).map((parentId) =>
cli.getRoom(parentId),
);
parents.forEach(parent => {
parents.forEach((parent) => {
if (set.has(parent)) return;
set.add(parent);
addAllParents(set, parent);
@ -88,22 +93,27 @@ const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [],
addAllParents(parents, room);
return [
Array.from(parents),
selected.map(roomId => {
const room = cli.getRoom(roomId);
if (!room) {
return { roomId, name: roomId } as Room;
}
if (room.getMyMembership() !== "join" || !room.isSpaceRoom()) {
return room;
}
}).filter(Boolean),
selected
.map((roomId) => {
const room = cli.getRoom(roomId);
if (!room) {
return { roomId, name: roomId } as Room;
}
if (room.getMyMembership() !== "join" || !room.isSpaceRoom()) {
return room;
}
})
.filter(Boolean),
];
}, [cli, selected, room]);
const [filteredSpacesContainingRoom, filteredOtherEntries] = useMemo(() => [
spacesContainingRoom.filter(r => r.name.toLowerCase().includes(lcQuery)),
otherEntries.filter(r => r.name.toLowerCase().includes(lcQuery)),
], [spacesContainingRoom, otherEntries, lcQuery]);
const [filteredSpacesContainingRoom, filteredOtherEntries] = useMemo(
() => [
spacesContainingRoom.filter((r) => r.name.toLowerCase().includes(lcQuery)),
otherEntries.filter((r) => r.name.toLowerCase().includes(lcQuery)),
],
[spacesContainingRoom, otherEntries, lcQuery],
);
const onChange = (checked: boolean, room: Room): void => {
if (checked) {
@ -116,92 +126,100 @@ const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [],
let inviteOnlyWarning;
if (newSelected.size < 1) {
inviteOnlyWarning = <div className="mx_ManageRestrictedJoinRuleDialog_section_info">
{ _t("You're removing all spaces. Access will default to invite only") }
</div>;
inviteOnlyWarning = (
<div className="mx_ManageRestrictedJoinRuleDialog_section_info">
{_t("You're removing all spaces. Access will default to invite only")}
</div>
);
}
return <BaseDialog
title={_t("Select spaces")}
className="mx_ManageRestrictedJoinRuleDialog"
onFinished={onFinished}
fixedWidth={false}
>
<p>
{ _t("Decide which spaces can access this room. " +
"If a space is selected, its members can find and join <RoomName/>.", {}, {
RoomName: () => <b>{ room.name }</b>,
}) }
</p>
<MatrixClientContext.Provider value={cli}>
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={_t("Search spaces")}
onSearch={setQuery}
autoFocus={true}
/>
<AutoHideScrollbar className="mx_ManageRestrictedJoinRuleDialog_content">
{ filteredSpacesContainingRoom.length > 0 ? (
<div className="mx_ManageRestrictedJoinRuleDialog_section">
<h3>
{ room.isSpaceRoom()
? _t("Spaces you know that contain this space")
: _t("Spaces you know that contain this room") }
</h3>
{ filteredSpacesContainingRoom.map(space => {
return <Entry
key={space.roomId}
room={space}
checked={newSelected.has(space.roomId)}
onChange={(checked: boolean) => {
onChange(checked, space);
}}
/>;
}) }
</div>
) : undefined }
{ filteredOtherEntries.length > 0 ? (
<div className="mx_ManageRestrictedJoinRuleDialog_section">
<h3>{ _t("Other spaces or rooms you might not know") }</h3>
<div className="mx_ManageRestrictedJoinRuleDialog_section_info">
<div>{ _t("These are likely ones other room admins are a part of.") }</div>
return (
<BaseDialog
title={_t("Select spaces")}
className="mx_ManageRestrictedJoinRuleDialog"
onFinished={onFinished}
fixedWidth={false}
>
<p>
{_t(
"Decide which spaces can access this room. " +
"If a space is selected, its members can find and join <RoomName/>.",
{},
{
RoomName: () => <b>{room.name}</b>,
},
)}
</p>
<MatrixClientContext.Provider value={cli}>
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={_t("Search spaces")}
onSearch={setQuery}
autoFocus={true}
/>
<AutoHideScrollbar className="mx_ManageRestrictedJoinRuleDialog_content">
{filteredSpacesContainingRoom.length > 0 ? (
<div className="mx_ManageRestrictedJoinRuleDialog_section">
<h3>
{room.isSpaceRoom()
? _t("Spaces you know that contain this space")
: _t("Spaces you know that contain this room")}
</h3>
{filteredSpacesContainingRoom.map((space) => {
return (
<Entry
key={space.roomId}
room={space}
checked={newSelected.has(space.roomId)}
onChange={(checked: boolean) => {
onChange(checked, space);
}}
/>
);
})}
</div>
{ filteredOtherEntries.map(space => {
return <Entry
key={space.roomId}
room={space}
checked={newSelected.has(space.roomId)}
onChange={(checked: boolean) => {
onChange(checked, space);
}}
/>;
}) }
) : undefined}
{filteredOtherEntries.length > 0 ? (
<div className="mx_ManageRestrictedJoinRuleDialog_section">
<h3>{_t("Other spaces or rooms you might not know")}</h3>
<div className="mx_ManageRestrictedJoinRuleDialog_section_info">
<div>{_t("These are likely ones other room admins are a part of.")}</div>
</div>
{filteredOtherEntries.map((space) => {
return (
<Entry
key={space.roomId}
room={space}
checked={newSelected.has(space.roomId)}
onChange={(checked: boolean) => {
onChange(checked, space);
}}
/>
);
})}
</div>
) : null}
{filteredSpacesContainingRoom.length + filteredOtherEntries.length < 1 ? (
<span className="mx_ManageRestrictedJoinRuleDialog_noResults">{_t("No results")}</span>
) : undefined}
</AutoHideScrollbar>
<div className="mx_ManageRestrictedJoinRuleDialog_footer">
{inviteOnlyWarning}
<div className="mx_ManageRestrictedJoinRuleDialog_footer_buttons">
<AccessibleButton kind="primary_outline" onClick={() => onFinished()}>
{_t("Cancel")}
</AccessibleButton>
<AccessibleButton kind="primary" onClick={() => onFinished(Array.from(newSelected))}>
{_t("Confirm")}
</AccessibleButton>
</div>
) : null }
{ filteredSpacesContainingRoom.length + filteredOtherEntries.length < 1
? <span className="mx_ManageRestrictedJoinRuleDialog_noResults">
{ _t("No results") }
</span>
: undefined
}
</AutoHideScrollbar>
<div className="mx_ManageRestrictedJoinRuleDialog_footer">
{ inviteOnlyWarning }
<div className="mx_ManageRestrictedJoinRuleDialog_footer_buttons">
<AccessibleButton kind="primary_outline" onClick={() => onFinished()}>
{ _t("Cancel") }
</AccessibleButton>
<AccessibleButton kind="primary" onClick={() => onFinished(Array.from(newSelected))}>
{ _t("Confirm") }
</AccessibleButton>
</div>
</div>
</MatrixClientContext.Provider>
</BaseDialog>;
</MatrixClientContext.Provider>
</BaseDialog>
);
};
export default ManageRestrictedJoinRuleDialog;

View file

@ -18,12 +18,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import * as FormattingUtils from "../../../utils/FormattingUtils";
import { _t } from "../../../languageHandler";
import QuestionDialog from "./QuestionDialog";
import { IDialogProps } from "./IDialogProps";
@ -35,9 +35,7 @@ interface IProps extends IDialogProps {
export default class ManualDeviceKeyVerificationDialog extends React.Component<IProps> {
private onLegacyFinished = (confirm: boolean): void => {
if (confirm) {
MatrixClientPeg.get().setDeviceVerified(
this.props.userId, this.props.device.deviceId, true,
);
MatrixClientPeg.get().setDeviceVerified(this.props.userId, this.props.device.deviceId, true);
}
this.props.onFinished(confirm);
};
@ -53,19 +51,29 @@ export default class ManualDeviceKeyVerificationDialog extends React.Component<I
const key = FormattingUtils.formatCryptoKey(this.props.device.getFingerprint());
const body = (
<div>
<p>
{ text }
</p>
<p>{text}</p>
<div className="mx_DeviceVerifyDialog_cryptoSection">
<ul>
<li><label>{ _t("Session name") }:</label> <span>{ this.props.device.getDisplayName() }</span></li>
<li><label>{ _t("Session ID") }:</label> <span><code>{ this.props.device.deviceId }</code></span></li>
<li><label>{ _t("Session key") }:</label> <span><code><b>{ key }</b></code></span></li>
<li>
<label>{_t("Session name")}:</label> <span>{this.props.device.getDisplayName()}</span>
</li>
<li>
<label>{_t("Session ID")}:</label>{" "}
<span>
<code>{this.props.device.deviceId}</code>
</span>
</li>
<li>
<label>{_t("Session key")}:</label>{" "}
<span>
<code>
<b>{key}</b>
</code>
</span>
</li>
</ul>
</div>
<p>
{ _t("If they don't match, the security of your communication may be compromised.") }
</p>
<p>{_t("If they don't match, the security of your communication may be compromised.")}</p>
</div>
);

View file

@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
import { defer } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from '../../../languageHandler';
import { wantsDateSeparator } from '../../../DateUtils';
import SettingsStore from '../../../settings/SettingsStore';
import { _t } from "../../../languageHandler";
import { wantsDateSeparator } from "../../../DateUtils";
import SettingsStore from "../../../settings/SettingsStore";
import BaseDialog from "./BaseDialog";
import ScrollPanel from "../../structures/ScrollPanel";
import Spinner from "../elements/Spinner";
@ -86,15 +86,18 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
const newEvents = result.events;
this.locallyRedactEventsIfNeeded(newEvents);
this.setState({
originalEvent: this.state.originalEvent || result.originalEvent,
events: this.state.events.concat(newEvents),
nextBatch: result.nextBatch,
isLoading: false,
}, () => {
const hasMoreResults = !!this.state.nextBatch;
resolve(hasMoreResults);
});
this.setState(
{
originalEvent: this.state.originalEvent || result.originalEvent,
events: this.state.events.concat(newEvents),
nextBatch: result.nextBatch,
isLoading: false,
},
() => {
const hasMoreResults = !!this.state.nextBatch;
resolve(hasMoreResults);
},
);
return promise;
};
@ -104,7 +107,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
const room = client.getRoom(roomId);
const pendingEvents = room.getPendingEvents();
for (const e of newEvents) {
const pendingRedaction = pendingEvents.find(pe => {
const pendingRedaction = pendingEvents.find((pe) => {
return pe.getType() === EventType.RoomRedaction && pe.getAssociatedId() === e.getId();
});
if (pendingRedaction) {
@ -128,18 +131,22 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
const baseEventId = this.props.mxEvent.getId();
allEvents.forEach((e, i) => {
if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) {
nodes.push(<li key={e.getTs() + "~"}><DateSeparator roomId={e.getRoomId()} ts={e.getTs()} /></li>);
nodes.push(
<li key={e.getTs() + "~"}>
<DateSeparator roomId={e.getRoomId()} ts={e.getTs()} />
</li>,
);
}
const isBaseEvent = e.getId() === baseEventId;
nodes.push((
nodes.push(
<EditHistoryMessage
key={e.getId()}
previousEdit={!isBaseEvent ? allEvents[i + 1] : null}
isBaseEvent={isBaseEvent}
mxEvent={e}
isTwelveHour={this.state.isTwelveHour}
/>
));
/>,
);
lastEvent = e;
});
return nodes;
@ -150,41 +157,45 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
if (this.state.error) {
const { error } = this.state;
if (error.errcode === "M_UNRECOGNIZED") {
content = (<p className="mx_MessageEditHistoryDialog_error">
{ _t("Your homeserver doesn't seem to support this feature.") }
</p>);
content = (
<p className="mx_MessageEditHistoryDialog_error">
{_t("Your homeserver doesn't seem to support this feature.")}
</p>
);
} else if (error.errcode) {
// some kind of error from the homeserver
content = (<p className="mx_MessageEditHistoryDialog_error">
{ _t("Something went wrong!") }
</p>);
content = <p className="mx_MessageEditHistoryDialog_error">{_t("Something went wrong!")}</p>;
} else {
content = (<p className="mx_MessageEditHistoryDialog_error">
{ _t("Cannot reach homeserver") }
<br />
{ _t("Ensure you have a stable internet connection, or get in touch with the server admin") }
</p>);
content = (
<p className="mx_MessageEditHistoryDialog_error">
{_t("Cannot reach homeserver")}
<br />
{_t("Ensure you have a stable internet connection, or get in touch with the server admin")}
</p>
);
}
} else if (this.state.isLoading) {
content = <Spinner />;
} else {
content = (<ScrollPanel
className="mx_MessageEditHistoryDialog_scrollPanel"
onFillRequest={this.loadMoreEdits}
stickyBottom={false}
startAtBottom={false}
>
<ul className="mx_MessageEditHistoryDialog_edits">{ this.renderEdits() }</ul>
</ScrollPanel>);
content = (
<ScrollPanel
className="mx_MessageEditHistoryDialog_scrollPanel"
onFillRequest={this.loadMoreEdits}
stickyBottom={false}
startAtBottom={false}
>
<ul className="mx_MessageEditHistoryDialog_edits">{this.renderEdits()}</ul>
</ScrollPanel>
);
}
return (
<BaseDialog
className='mx_MessageEditHistoryDialog'
className="mx_MessageEditHistoryDialog"
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Message edits")}
>
{ content }
{content}
</BaseDialog>
);
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as React from 'react';
import * as React from "react";
import {
ClientWidgetApi,
IModalWidgetCloseRequest,
@ -31,8 +31,8 @@ import {
WidgetKind,
} from "matrix-widget-api";
import BaseDialog from './BaseDialog';
import { _t, getUserLanguage } from '../../../languageHandler';
import BaseDialog from "./BaseDialog";
import { _t, getUserLanguage } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import { StopGapWidgetDriver } from "../../../stores/widgets/StopGapWidgetDriver";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
@ -62,8 +62,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
state: IState = {
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter(b => b.disabled)
.map(b => b.id),
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter((b) => b.disabled).map((b) => b.id),
};
constructor(props) {
@ -74,7 +73,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
creatorUserId: MatrixClientPeg.get().getUserId(),
id: `modal_${this.props.sourceWidgetId}`,
});
this.possibleButtons = (this.props.widgetDefinition.buttons || []).map(b => b.id);
this.possibleButtons = (this.props.widgetDefinition.buttons || []).map((b) => b.id);
}
public componentDidMount() {
@ -114,7 +113,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
let buttonIds: ModalButtonID[];
if (ev.detail.data.enabled) {
buttonIds = arrayFastClone(this.state.disabledButtonIds).filter(i => i !== ev.detail.data.button);
buttonIds = arrayFastClone(this.state.disabledButtonIds).filter((i) => i !== ev.detail.data.button);
} else {
// use a set to swap the operation to avoid memory leaky arrays.
const tempSet = new Set(this.state.disabledButtonIds);
@ -141,71 +140,76 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
// Add in some legacy support sprinkles (for non-popout widgets)
// TODO: Replace these with proper widget params
// See https://github.com/matrix-org/matrix-doc/pull/1958/files#r405714833
parsed.searchParams.set('widgetId', this.widget.id);
parsed.searchParams.set('parentUrl', window.location.href.split('#', 2)[0]);
parsed.searchParams.set("widgetId", this.widget.id);
parsed.searchParams.set("parentUrl", window.location.href.split("#", 2)[0]);
// Replace the encoded dollar signs back to dollar signs. They have no special meaning
// in HTTP, but URL parsers encode them anyways.
const widgetUrl = parsed.toString().replace(/%24/g, '$');
const widgetUrl = parsed.toString().replace(/%24/g, "$");
let buttons;
if (this.props.widgetDefinition.buttons) {
// show first button rightmost for a more natural specification
buttons = this.props.widgetDefinition.buttons.slice(0, MAX_BUTTONS).reverse().map(def => {
let kind = "secondary";
switch (def.kind) {
case ModalButtonKind.Primary:
kind = "primary";
break;
case ModalButtonKind.Secondary:
kind = "primary_outline";
break;
case ModalButtonKind.Danger:
kind = "danger";
break;
}
buttons = this.props.widgetDefinition.buttons
.slice(0, MAX_BUTTONS)
.reverse()
.map((def) => {
let kind = "secondary";
switch (def.kind) {
case ModalButtonKind.Primary:
kind = "primary";
break;
case ModalButtonKind.Secondary:
kind = "primary_outline";
break;
case ModalButtonKind.Danger:
kind = "danger";
break;
}
const onClick = () => {
this.state.messaging.notifyModalWidgetButtonClicked(def.id);
};
const onClick = () => {
this.state.messaging.notifyModalWidgetButtonClicked(def.id);
};
const isDisabled = this.state.disabledButtonIds.includes(def.id);
const isDisabled = this.state.disabledButtonIds.includes(def.id);
return <AccessibleButton key={def.id} kind={kind} onClick={onClick} disabled={isDisabled}>
{ def.label }
</AccessibleButton>;
});
return (
<AccessibleButton key={def.id} kind={kind} onClick={onClick} disabled={isDisabled}>
{def.label}
</AccessibleButton>
);
});
}
return <BaseDialog
title={this.props.widgetDefinition.name || _t("Modal Widget")}
className="mx_ModalWidgetDialog"
contentId="mx_Dialog_content"
onFinished={this.props.onFinished}
>
<div className="mx_ModalWidgetDialog_warning">
<img
src={require("../../../../res/img/element-icons/warning-badge.svg").default}
height="16"
width="16"
alt=""
/>
{ _t("Data on this screen is shared with %(widgetDomain)s", {
widgetDomain: parsed.hostname,
}) }
</div>
<div>
<iframe
title={this.widget.name}
ref={this.appFrame}
sandbox="allow-forms allow-scripts allow-same-origin"
src={widgetUrl}
onLoad={this.onLoad}
/>
</div>
<div className="mx_ModalWidgetDialog_buttons">
{ buttons }
</div>
</BaseDialog>;
return (
<BaseDialog
title={this.props.widgetDefinition.name || _t("Modal Widget")}
className="mx_ModalWidgetDialog"
contentId="mx_Dialog_content"
onFinished={this.props.onFinished}
>
<div className="mx_ModalWidgetDialog_warning">
<img
src={require("../../../../res/img/element-icons/warning-badge.svg").default}
height="16"
width="16"
alt=""
/>
{_t("Data on this screen is shared with %(widgetDomain)s", {
widgetDomain: parsed.hostname,
})}
</div>
<div>
<iframe
title={this.widget.name}
ref={this.appFrame}
sandbox="allow-forms allow-scripts allow-same-origin"
src={widgetUrl}
onLoad={this.onLoad}
/>
</div>
<div className="mx_ModalWidgetDialog_buttons">{buttons}</div>
</BaseDialog>
);
}
}

View file

@ -59,8 +59,10 @@ export class ModuleUiDialog extends ScrollableBaseModal<IProps, IState> {
}
protected renderContent(): React.ReactNode {
return <div className="mx_ModuleUiDialog">
{ this.props.contentFactory(this.props.contentProps, this.contentRef) }
</div>;
return (
<div className="mx_ModuleUiDialog">
{this.props.contentFactory(this.props.contentProps, this.contentRef)}
</div>
);
}
}

View file

@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
@ -68,15 +68,16 @@ export default class QuestionDialog extends React.Component<IQuestionDialogProps
className={classNames("mx_QuestionDialog", this.props.className)}
onFinished={this.props.onFinished}
title={this.props.title}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
headerImage={this.props.headerImage}
hasCancel={this.props.hasCancelButton}
fixedWidth={this.props.fixedWidth}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ this.props.description }
<div className="mx_Dialog_content" id="mx_Dialog_content">
{this.props.description}
</div>
<DialogButtons primaryButton={this.props.button || _t('OK')}
<DialogButtons
primaryButton={this.props.button || _t("OK")}
primaryButtonClass={primaryButtonClass}
primaryDisabled={this.props.buttonDisabled}
cancelButton={this.props.cancelButton}
@ -85,7 +86,7 @@ export default class QuestionDialog extends React.Component<IQuestionDialogProps
focus={this.props.focus}
onCancel={this.onCancel}
>
{ this.props.extraButtons }
{this.props.extraButtons}
</DialogButtons>
</BaseDialog>
);

View file

@ -17,7 +17,7 @@ limitations under the License.
import * as React from "react";
import { useRef, useState } from "react";
import { _t, _td } from '../../../languageHandler';
import { _t, _td } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import Field from "../elements/Field";
import BaseDialog from "./BaseDialog";
@ -47,37 +47,41 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {
onFinished(true, email);
};
return <BaseDialog
title={_t("Continuing without email")}
className="mx_RegistrationEmailPromptDialog"
contentId="mx_RegistrationEmailPromptDialog"
onFinished={() => onFinished(false)}
fixedWidth={false}
>
<div className="mx_Dialog_content" id="mx_RegistrationEmailPromptDialog">
<p>{ _t("Just a heads up, if you don't add an email and forget your password, you could " +
"<b>permanently lose access to your account</b>.", {}, {
b: sub => <b>{ sub }</b>,
}) }</p>
<form onSubmit={onSubmit}>
<EmailField
fieldRef={fieldRef}
autoFocus={true}
label={_td("Email (optional)")}
value={email}
onChange={ev => {
const target = ev.target as HTMLInputElement;
setEmail(target.value);
}}
/>
</form>
</div>
<DialogButtons
primaryButton={_t("Continue")}
onPrimaryButtonClick={onSubmit}
hasCancel={false}
/>
</BaseDialog>;
return (
<BaseDialog
title={_t("Continuing without email")}
className="mx_RegistrationEmailPromptDialog"
contentId="mx_RegistrationEmailPromptDialog"
onFinished={() => onFinished(false)}
fixedWidth={false}
>
<div className="mx_Dialog_content" id="mx_RegistrationEmailPromptDialog">
<p>
{_t(
"Just a heads up, if you don't add an email and forget your password, you could " +
"<b>permanently lose access to your account</b>.",
{},
{
b: (sub) => <b>{sub}</b>,
},
)}
</p>
<form onSubmit={onSubmit}>
<EmailField
fieldRef={fieldRef}
autoFocus={true}
label={_td("Email (optional)")}
value={email}
onChange={(ev) => {
const target = ev.target as HTMLInputElement;
setEmail(target.value);
}}
/>
</form>
</div>
<DialogButtons primaryButton={_t("Continue")} onPrimaryButtonClick={onSubmit} hasCancel={false} />
</BaseDialog>
);
};
export default RegistrationEmailPromptDialog;

View file

@ -15,16 +15,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import { ensureDMExists } from "../../../createRoom";
import { IDialogProps } from "./IDialogProps";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import SdkConfig from '../../../SdkConfig';
import Markdown from '../../../Markdown';
import SdkConfig from "../../../SdkConfig";
import Markdown from "../../../Markdown";
import SettingsStore from "../../../settings/SettingsStore";
import StyledRadioButton from "../elements/StyledRadioButton";
import BaseDialog from "./BaseDialog";
@ -71,7 +71,7 @@ enum NonStandardValue {
// Non-standard abuse nature.
// It should never leave the client - we use it to fallback to
// server-wide abuse reporting.
Admin = "non-standard.abuse.nature.admin"
Admin = "non-standard.abuse.nature.admin",
}
type ExtendedNature = Nature | NonStandardValue;
@ -117,30 +117,47 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
}
if (Array.isArray(stateEvent)) {
// Internal error.
throw new TypeError(`getStateEvents(${stateEventType}, ${stateEventType}) ` +
"should return at most one state event");
throw new TypeError(
`getStateEvents(${stateEventType}, ${stateEventType}) ` +
"should return at most one state event",
);
}
const event = stateEvent.event;
if (!("content" in event) || typeof event["content"] != "object") {
// The room is improperly configured.
// Display this debug message for the sake of moderators.
console.debug("Moderation error", "state event", stateEventType,
"should have an object field `content`, got", event);
console.debug(
"Moderation error",
"state event",
stateEventType,
"should have an object field `content`, got",
event,
);
continue;
}
const content = event["content"];
if (!("room_id" in content) || typeof content["room_id"] != "string") {
// The room is improperly configured.
// Display this debug message for the sake of moderators.
console.debug("Moderation error", "state event", stateEventType,
"should have a string field `content.room_id`, got", event);
console.debug(
"Moderation error",
"state event",
stateEventType,
"should have a string field `content.room_id`, got",
event,
);
continue;
}
if (!("user_id" in content) || typeof content["user_id"] != "string") {
// The room is improperly configured.
// Display this debug message for the sake of moderators.
console.debug("Moderation error", "state event", stateEventType,
"should have a string field `content.user_id`, got", event);
console.debug(
"Moderation error",
"state event",
stateEventType,
"should have a string field `content.user_id`, got",
event,
);
continue;
}
moderatedByRoomId = content["room_id"];
@ -194,9 +211,9 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
// This room supports moderation.
// We need a nature.
// If the nature is `NATURE.OTHER` or `NON_STANDARD_NATURE.ADMIN`, we also need a `reason`.
if (!this.state.nature ||
((this.state.nature == Nature.Other || this.state.nature == NonStandardValue.Admin)
&& !reason)
if (
!this.state.nature ||
((this.state.nature == Nature.Other || this.state.nature == NonStandardValue.Admin) && !reason)
) {
this.setState({
err: _t("Please fill why you're reporting."),
@ -243,10 +260,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
// if the user should also be ignored, do that
if (this.state.ignoreUserToo) {
await client.setIgnoredUsers([
...client.getIgnoredUsers(),
ev.getSender(),
]);
await client.setIgnoredUsers([...client.getIgnoredUsers(), ev.getSender()]);
}
this.props.onFinished(true);
@ -262,9 +276,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
public render() {
let error = null;
if (this.state.err) {
error = <div className="error">
{ this.state.err }
</div>;
error = <div className="error">{this.state.err}</div>;
}
let progress = null;
@ -276,16 +288,17 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
);
}
const ignoreUserCheckbox = <LabelledCheckbox
value={this.state.ignoreUserToo}
label={_t("Ignore user")}
byline={_t("Check if you want to hide all current and future messages from this user.")}
onChange={this.onIgnoreUserTooChanged}
disabled={this.state.busy}
/>;
const ignoreUserCheckbox = (
<LabelledCheckbox
value={this.state.ignoreUserToo}
label={_t("Ignore user")}
byline={_t("Check if you want to hide all current and future messages from this user.")}
onChange={this.onIgnoreUserTooChanged}
disabled={this.state.busy}
/>
);
const adminMessageMD = SdkConfig
.getObject("report_event")?.get("admin_message_md", "adminMessageMD");
const adminMessageMD = SdkConfig.getObject("report_event")?.get("admin_message_md", "adminMessageMD");
let adminMessage;
if (adminMessageMD) {
const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true });
@ -300,42 +313,55 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
let subtitle;
switch (this.state.nature) {
case Nature.Disagreement:
subtitle = _t("What this user is writing is wrong.\n" +
"This will be reported to the room moderators.");
subtitle = _t(
"What this user is writing is wrong.\n" + "This will be reported to the room moderators.",
);
break;
case Nature.Toxic:
subtitle = _t("This user is displaying toxic behaviour, " +
"for instance by insulting other users or sharing " +
" adult-only content in a family-friendly room " +
" or otherwise violating the rules of this room.\n" +
"This will be reported to the room moderators.");
subtitle = _t(
"This user is displaying toxic behaviour, " +
"for instance by insulting other users or sharing " +
" adult-only content in a family-friendly room " +
" or otherwise violating the rules of this room.\n" +
"This will be reported to the room moderators.",
);
break;
case Nature.Illegal:
subtitle = _t("This user is displaying illegal behaviour, " +
"for instance by doxing people or threatening violence.\n" +
"This will be reported to the room moderators who may escalate this to legal authorities.");
subtitle = _t(
"This user is displaying illegal behaviour, " +
"for instance by doxing people or threatening violence.\n" +
"This will be reported to the room moderators who may escalate this to legal authorities.",
);
break;
case Nature.Spam:
subtitle = _t("This user is spamming the room with ads, links to ads or to propaganda.\n" +
"This will be reported to the room moderators.");
subtitle = _t(
"This user is spamming the room with ads, links to ads or to propaganda.\n" +
"This will be reported to the room moderators.",
);
break;
case NonStandardValue.Admin:
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) {
subtitle = _t("This room is dedicated to illegal or toxic content " +
"or the moderators fail to moderate illegal or toxic content.\n" +
"This will be reported to the administrators of %(homeserver)s. " +
"The administrators will NOT be able to read the encrypted content of this room.",
{ homeserver: homeServerName });
subtitle = _t(
"This room is dedicated to illegal or toxic content " +
"or the moderators fail to moderate illegal or toxic content.\n" +
"This will be reported to the administrators of %(homeserver)s. " +
"The administrators will NOT be able to read the encrypted content of this room.",
{ homeserver: homeServerName },
);
} else {
subtitle = _t("This room is dedicated to illegal or toxic content " +
"or the moderators fail to moderate illegal or toxic content.\n" +
" This will be reported to the administrators of %(homeserver)s.",
{ homeserver: homeServerName });
subtitle = _t(
"This room is dedicated to illegal or toxic content " +
"or the moderators fail to moderate illegal or toxic content.\n" +
" This will be reported to the administrators of %(homeserver)s.",
{ homeserver: homeServerName },
);
}
break;
case Nature.Other:
subtitle = _t("Any other reason. Please describe the problem.\n" +
"This will be reported to the room moderators.");
subtitle = _t(
"Any other reason. Please describe the problem.\n" +
"This will be reported to the room moderators.",
);
break;
default:
subtitle = _t("Please pick a nature and describe what makes this message abusive.");
@ -346,8 +372,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
<BaseDialog
className="mx_ReportEventDialog"
onFinished={this.props.onFinished}
title={_t('Report Content')}
contentId='mx_ReportEventDialog'
title={_t("Report Content")}
contentId="mx_ReportEventDialog"
>
<div>
<StyledRadioButton
@ -356,7 +382,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
checked={this.state.nature == Nature.Disagreement}
onChange={this.onNatureChosen}
>
{ _t('Disagree') }
{_t("Disagree")}
</StyledRadioButton>
<StyledRadioButton
name="nature"
@ -364,7 +390,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
checked={this.state.nature == Nature.Toxic}
onChange={this.onNatureChosen}
>
{ _t('Toxic Behaviour') }
{_t("Toxic Behaviour")}
</StyledRadioButton>
<StyledRadioButton
name="nature"
@ -372,7 +398,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
checked={this.state.nature == Nature.Illegal}
onChange={this.onNatureChosen}
>
{ _t('Illegal Content') }
{_t("Illegal Content")}
</StyledRadioButton>
<StyledRadioButton
name="nature"
@ -380,7 +406,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
checked={this.state.nature == Nature.Spam}
onChange={this.onNatureChosen}
>
{ _t('Spam or propaganda') }
{_t("Spam or propaganda")}
</StyledRadioButton>
<StyledRadioButton
name="nature"
@ -388,7 +414,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
checked={this.state.nature == NonStandardValue.Admin}
onChange={this.onNatureChosen}
>
{ _t('Report the entire room') }
{_t("Report the entire room")}
</StyledRadioButton>
<StyledRadioButton
name="nature"
@ -396,11 +422,9 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
checked={this.state.nature == Nature.Other}
onChange={this.onNatureChosen}
>
{ _t('Other') }
{_t("Other")}
</StyledRadioButton>
<p>
{ subtitle }
</p>
<p>{subtitle}</p>
<Field
className="mx_ReportEventDialog_reason"
element="textarea"
@ -410,9 +434,9 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
value={this.state.reason}
disabled={this.state.busy}
/>
{ progress }
{ error }
{ ignoreUserCheckbox }
{progress}
{error}
{ignoreUserCheckbox}
</div>
<DialogButtons
primaryButton={_t("Send report")}
@ -430,19 +454,19 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
<BaseDialog
className="mx_ReportEventDialog"
onFinished={this.props.onFinished}
title={_t('Report Content to Your Homeserver Administrator')}
contentId='mx_ReportEventDialog'
title={_t("Report Content to Your Homeserver Administrator")}
contentId="mx_ReportEventDialog"
>
<div className="mx_ReportEventDialog" id="mx_ReportEventDialog">
<p>
{
_t("Reporting this message will send its unique 'event ID' to the administrator of " +
{_t(
"Reporting this message will send its unique 'event ID' to the administrator of " +
"your homeserver. If messages in this room are encrypted, your homeserver " +
"administrator will not be able to read the message text or view any files " +
"or images.")
}
"or images.",
)}
</p>
{ adminMessage }
{adminMessage}
<Field
className="mx_ReportEventDialog_reason"
element="textarea"
@ -452,9 +476,9 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
value={this.state.reason}
disabled={this.state.busy}
/>
{ progress }
{ error }
{ ignoreUserCheckbox }
{progress}
{error}
{ignoreUserCheckbox}
</div>
<DialogButtons
primaryButton={_t("Send report")}

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { RoomEvent } from "matrix-js-sdk/src/models/room";
import TabbedView, { Tab } from "../../structures/TabbedView";
@ -31,7 +31,7 @@ import dis from "../../../dispatcher/dispatcher";
import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
import BaseDialog from "./BaseDialog";
import { Action } from '../../../dispatcher/actions';
import { Action } from "../../../dispatcher/actions";
import { VoipRoomSettingsTab } from "../settings/tabs/room/VoipRoomSettingsTab";
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
@ -57,7 +57,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
constructor(props: IProps) {
super(props);
this.state = { roomName: '' };
this.state = { roomName: "" };
}
public componentDidMount() {
@ -91,67 +91,90 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
private getTabs(): Tab[] {
const tabs: Tab[] = [];
tabs.push(new Tab(
ROOM_GENERAL_TAB,
_td("General"),
"mx_RoomSettingsDialog_settingsIcon",
<GeneralRoomSettingsTab roomId={this.props.roomId} />,
"RoomSettingsGeneral",
));
tabs.push(
new Tab(
ROOM_GENERAL_TAB,
_td("General"),
"mx_RoomSettingsDialog_settingsIcon",
<GeneralRoomSettingsTab roomId={this.props.roomId} />,
"RoomSettingsGeneral",
),
);
if (SettingsStore.getValue("feature_group_calls")) {
tabs.push(new Tab(
ROOM_VOIP_TAB,
_td("Voice & Video"),
"mx_RoomSettingsDialog_voiceIcon",
<VoipRoomSettingsTab roomId={this.props.roomId} />,
));
tabs.push(
new Tab(
ROOM_VOIP_TAB,
_td("Voice & Video"),
"mx_RoomSettingsDialog_voiceIcon",
<VoipRoomSettingsTab roomId={this.props.roomId} />,
),
);
}
tabs.push(new Tab(
ROOM_SECURITY_TAB,
_td("Security & Privacy"),
"mx_RoomSettingsDialog_securityIcon",
<SecurityRoomSettingsTab
roomId={this.props.roomId}
closeSettingsFn={() => this.props.onFinished(true)}
/>,
"RoomSettingsSecurityPrivacy",
));
tabs.push(new Tab(
ROOM_ROLES_TAB,
_td("Roles & Permissions"),
"mx_RoomSettingsDialog_rolesIcon",
<RolesRoomSettingsTab roomId={this.props.roomId} />,
"RoomSettingsRolesPermissions",
));
tabs.push(new Tab(
ROOM_NOTIFICATIONS_TAB,
_td("Notifications"),
"mx_RoomSettingsDialog_notificationsIcon",
<NotificationSettingsTab roomId={this.props.roomId} closeSettingsFn={() => this.props.onFinished(true)} />,
"RoomSettingsNotifications",
));
tabs.push(
new Tab(
ROOM_SECURITY_TAB,
_td("Security & Privacy"),
"mx_RoomSettingsDialog_securityIcon",
(
<SecurityRoomSettingsTab
roomId={this.props.roomId}
closeSettingsFn={() => this.props.onFinished(true)}
/>
),
"RoomSettingsSecurityPrivacy",
),
);
tabs.push(
new Tab(
ROOM_ROLES_TAB,
_td("Roles & Permissions"),
"mx_RoomSettingsDialog_rolesIcon",
<RolesRoomSettingsTab roomId={this.props.roomId} />,
"RoomSettingsRolesPermissions",
),
);
tabs.push(
new Tab(
ROOM_NOTIFICATIONS_TAB,
_td("Notifications"),
"mx_RoomSettingsDialog_notificationsIcon",
(
<NotificationSettingsTab
roomId={this.props.roomId}
closeSettingsFn={() => this.props.onFinished(true)}
/>
),
"RoomSettingsNotifications",
),
);
if (SettingsStore.getValue("feature_bridge_state")) {
tabs.push(new Tab(
ROOM_BRIDGES_TAB,
_td("Bridges"),
"mx_RoomSettingsDialog_bridgesIcon",
<BridgeSettingsTab roomId={this.props.roomId} />,
"RoomSettingsBridges",
));
tabs.push(
new Tab(
ROOM_BRIDGES_TAB,
_td("Bridges"),
"mx_RoomSettingsDialog_bridgesIcon",
<BridgeSettingsTab roomId={this.props.roomId} />,
"RoomSettingsBridges",
),
);
}
if (SettingsStore.getValue(UIFeature.AdvancedSettings)) {
tabs.push(new Tab(
ROOM_ADVANCED_TAB,
_td("Advanced"),
"mx_RoomSettingsDialog_warningIcon",
<AdvancedRoomSettingsTab
roomId={this.props.roomId}
closeSettingsFn={() => this.props.onFinished(true)}
/>,
"RoomSettingsAdvanced",
));
tabs.push(
new Tab(
ROOM_ADVANCED_TAB,
_td("Advanced"),
"mx_RoomSettingsDialog_warningIcon",
(
<AdvancedRoomSettingsTab
roomId={this.props.roomId}
closeSettingsFn={() => this.props.onFinished(true)}
/>
),
"RoomSettingsAdvanced",
),
);
}
return tabs;
@ -161,12 +184,12 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
const roomName = this.state.roomName;
return (
<BaseDialog
className='mx_RoomSettingsDialog'
className="mx_RoomSettingsDialog"
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Room Settings - %(roomName)s", { roomName })}
>
<div className='mx_SettingsDialog_content'>
<div className="mx_SettingsDialog_content">
<TabbedView
tabs={this.getTabs()}
initialTabId={this.props.initialTabId}

View file

@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import { upgradeRoom } from "../../../utils/RoomUpgrade";
import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import ErrorDialog from './ErrorDialog';
import DialogButtons from '../elements/DialogButtons';
import ErrorDialog from "./ErrorDialog";
import DialogButtons from "../elements/DialogButtons";
import Spinner from "../elements/Spinner";
interface IProps extends IDialogProps {
@ -53,16 +53,19 @@ export default class RoomUpgradeDialog extends React.Component<IProps, IState> {
private onUpgradeClick = (): void => {
this.setState({ busy: true });
upgradeRoom(this.props.room, this.targetVersion, false, false).then(() => {
this.props.onFinished(true);
}).catch((err) => {
Modal.createDialog(ErrorDialog, {
title: _t("Failed to upgrade room"),
description: ((err && err.message) ? err.message : _t("The room upgrade could not be completed")),
upgradeRoom(this.props.room, this.targetVersion, false, false)
.then(() => {
this.props.onFinished(true);
})
.catch((err) => {
Modal.createDialog(ErrorDialog, {
title: _t("Failed to upgrade room"),
description: err && err.message ? err.message : _t("The room upgrade could not be completed"),
});
})
.finally(() => {
this.setState({ busy: false });
});
}).finally(() => {
this.setState({ busy: false });
});
};
render() {
@ -70,13 +73,15 @@ export default class RoomUpgradeDialog extends React.Component<IProps, IState> {
if (this.state.busy) {
buttons = <Spinner />;
} else {
buttons = <DialogButtons
primaryButton={_t('Upgrade this room to version %(version)s', { version: this.targetVersion })}
primaryButtonClass="danger"
hasCancel={true}
onPrimaryButtonClick={this.onUpgradeClick}
onCancel={this.onCancelClick}
/>;
buttons = (
<DialogButtons
primaryButton={_t("Upgrade this room to version %(version)s", { version: this.targetVersion })}
primaryButtonClass="danger"
hasCancel={true}
onPrimaryButtonClick={this.onUpgradeClick}
onCancel={this.onCancelClick}
/>
);
}
return (
@ -84,25 +89,33 @@ export default class RoomUpgradeDialog extends React.Component<IProps, IState> {
className="mx_RoomUpgradeDialog"
onFinished={this.props.onFinished}
title={_t("Upgrade Room Version")}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
hasCancel={true}
>
<p>
{ _t(
{_t(
"Upgrading this room requires closing down the current " +
"instance of the room and creating a new room in its place. " +
"To give room members the best possible experience, we will:",
) }
"instance of the room and creating a new room in its place. " +
"To give room members the best possible experience, we will:",
)}
</p>
<ol>
<li>{ _t("Create a new room with the same name, description and avatar") }</li>
<li>{ _t("Update any local room aliases to point to the new room") }</li>
<li>{ _t("Stop users from speaking in the old version of the room, " +
"and post a message advising users to move to the new room") }</li>
<li>{ _t("Put a link back to the old room at the start of the new room " +
"so people can see old messages") }</li>
<li>{_t("Create a new room with the same name, description and avatar")}</li>
<li>{_t("Update any local room aliases to point to the new room")}</li>
<li>
{_t(
"Stop users from speaking in the old version of the room, " +
"and post a message advising users to move to the new room",
)}
</li>
<li>
{_t(
"Put a link back to the old room at the start of the new room " +
"so people can see old messages",
)}
</li>
</ol>
{ buttons }
{buttons}
</BaseDialog>
);
}

View file

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactNode } from 'react';
import { EventType } from 'matrix-js-sdk/src/@types/event';
import { JoinRule } from 'matrix-js-sdk/src/@types/partials';
import React, { ReactNode } from "react";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
@ -24,11 +24,11 @@ import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import Modal from "../../../Modal";
import { IDialogProps } from "./IDialogProps";
import BugReportDialog from './BugReportDialog';
import BugReportDialog from "./BugReportDialog";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import ProgressBar from "../elements/ProgressBar";
import AccessibleButton from '../elements/AccessibleButton';
import AccessibleButton from "../elements/AccessibleButton";
export interface IFinishedOpts {
continue: boolean;
@ -58,7 +58,7 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
const joinRules = room?.currentState.getStateEvents(EventType.RoomJoinRules, "");
this.isPrivate = joinRules?.getContent()['join_rule'] !== JoinRule.Public ?? true;
this.isPrivate = joinRules?.getContent()["join_rule"] !== JoinRule.Public ?? true;
this.currentVersion = room?.getVersion();
this.state = {
@ -109,7 +109,8 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
<LabelledToggleSwitch
value={this.state.inviteUsersToNewRoom}
onChange={this.onInviteUsersToggle}
label={_t("Automatically invite members from this room to the new one")} />
label={_t("Automatically invite members from this room to the new one")}
/>
);
}
@ -117,53 +118,58 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
let bugReports = (
<p>
{ _t(
{_t(
"This usually only affects how the room is processed on the server. If you're " +
"having problems with your %(brand)s, please report a bug.", { brand },
) }
"having problems with your %(brand)s, please report a bug.",
{ brand },
)}
</p>
);
if (SdkConfig.get().bug_report_endpoint_url) {
bugReports = (
<p>
{ _t(
{_t(
"This usually only affects how the room is processed on the server. If you're " +
"having problems with your %(brand)s, please <a>report a bug</a>.",
"having problems with your %(brand)s, please <a>report a bug</a>.",
{
brand,
},
{
"a": (sub) => {
return <AccessibleButton kind='link_inline' onClick={this.openBugReportDialog}>
{ sub }
</AccessibleButton>;
a: (sub) => {
return (
<AccessibleButton kind="link_inline" onClick={this.openBugReportDialog}>
{sub}
</AccessibleButton>
);
},
},
) }
)}
</p>
);
}
let footer: JSX.Element;
if (this.state.progressText) {
footer = <span className="mx_RoomUpgradeWarningDialog_progress">
<ProgressBar value={this.state.progress} max={this.state.total} />
<div className="mx_RoomUpgradeWarningDialog_progressText">
{ this.state.progressText }
</div>
</span>;
footer = (
<span className="mx_RoomUpgradeWarningDialog_progress">
<ProgressBar value={this.state.progress} max={this.state.total} />
<div className="mx_RoomUpgradeWarningDialog_progressText">{this.state.progressText}</div>
</span>
);
} else {
footer = <DialogButtons
primaryButton={_t("Upgrade")}
onPrimaryButtonClick={this.onContinue}
cancelButton={_t("Cancel")}
onCancel={this.onCancel}
/>;
footer = (
<DialogButtons
primaryButton={_t("Upgrade")}
onPrimaryButtonClick={this.onContinue}
cancelButton={_t("Cancel")}
onCancel={this.onCancel}
/>
);
}
return (
<BaseDialog
className='mx_RoomUpgradeWarningDialog'
className="mx_RoomUpgradeWarningDialog"
hasCancel={true}
fixedWidth={false}
onFinished={this.props.onFinished}
@ -171,33 +177,36 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
>
<div>
<p>
{ this.props.description || _t(
"Upgrading a room is an advanced action and is usually recommended when a room " +
"is unstable due to bugs, missing features or security vulnerabilities.",
) }
{this.props.description ||
_t(
"Upgrading a room is an advanced action and is usually recommended when a room " +
"is unstable due to bugs, missing features or security vulnerabilities.",
)}
</p>
<p>
{ _t(
{_t(
"<b>Please note upgrading will make a new version of the room</b>. " +
"All current messages will stay in this archived room.", {}, {
b: sub => <b>{ sub }</b>,
"All current messages will stay in this archived room.",
{},
{
b: (sub) => <b>{sub}</b>,
},
) }
)}
</p>
{ bugReports }
{bugReports}
<p>
{ _t(
{_t(
"You'll upgrade this room from <oldVersion /> to <newVersion />.",
{},
{
oldVersion: () => <code>{ this.currentVersion }</code>,
newVersion: () => <code>{ this.props.targetVersion }</code>,
oldVersion: () => <code>{this.currentVersion}</code>,
newVersion: () => <code>{this.props.targetVersion}</code>,
},
) }
)}
</p>
{ inviteToggle }
{inviteToggle}
</div>
{ footer }
{footer}
</BaseDialog>
);
}

View file

@ -35,8 +35,10 @@ export interface IScrollableBaseState {
/**
* Scrollable dialog base from Compound (Web Components).
*/
export default abstract class ScrollableBaseModal<TProps extends IDialogProps, TState extends IScrollableBaseState>
extends React.PureComponent<TProps, TState> {
export default abstract class ScrollableBaseModal<
TProps extends IDialogProps,
TState extends IScrollableBaseState,
> extends React.PureComponent<TProps, TState> {
protected constructor(props: TProps) {
super(props);
}
@ -87,7 +89,7 @@ export default abstract class ScrollableBaseModal<TProps extends IDialogProps, T
className="mx_CompoundDialog mx_ScrollableBaseDialog"
>
<div className="mx_CompoundDialog_header">
<h1>{ this.state.title }</h1>
<h1>{this.state.title}</h1>
<AccessibleButton
onClick={this.onCancel}
className="mx_CompoundDialog_cancelButton"
@ -95,12 +97,10 @@ export default abstract class ScrollableBaseModal<TProps extends IDialogProps, T
/>
</div>
<form onSubmit={this.onSubmit}>
<div className="mx_CompoundDialog_content">
{ this.renderContent() }
</div>
<div className="mx_CompoundDialog_content">{this.renderContent()}</div>
<div className="mx_CompoundDialog_footer">
<AccessibleButton onClick={this.onCancel} kind="primary_outline">
{ _t("Cancel") }
{_t("Cancel")}
</AccessibleButton>
<AccessibleButton
onClick={this.onSubmit}
@ -110,7 +110,7 @@ export default abstract class ScrollableBaseModal<TProps extends IDialogProps, T
element="button"
className="mx_Dialog_nonDialogButton"
>
{ this.state.actionLabel }
{this.state.actionLabel}
</AccessibleButton>
</div>
</form>

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as React from 'react';
import * as React from "react";
import BaseDialog from './BaseDialog';
import { _t } from '../../../languageHandler';
import BaseDialog from "./BaseDialog";
import { _t } from "../../../languageHandler";
import { EchoStore } from "../../../stores/local-echo/EchoStore";
import { formatTime } from "../../../DateUtils";
import SettingsStore from "../../../settings/SettingsStore";
@ -30,8 +30,7 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { IDialogProps } from "./IDialogProps";
interface IProps extends IDialogProps {
}
interface IProps extends IDialogProps {}
export default class ServerOfflineDialog extends React.PureComponent<IProps> {
public componentDidMount() {
@ -53,35 +52,35 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
const header = (
<div className="mx_ServerOfflineDialog_content_context_timeline_header">
<RoomAvatar width={24} height={24} room={c.room} />
<span>{ c.room.name }</span>
<span>{c.room.name}</span>
</div>
);
const entries = c.transactions
.filter(t => t.status === TransactionStatus.Error || t.didPreviouslyFail)
.filter((t) => t.status === TransactionStatus.Error || t.didPreviouslyFail)
.map((t, j) => {
let button = <Spinner w={19} h={19} />;
if (t.status === TransactionStatus.Error) {
button = (
<AccessibleButton kind="link" onClick={() => t.run()}>{ _t("Resend") }</AccessibleButton>
<AccessibleButton kind="link" onClick={() => t.run()}>
{_t("Resend")}
</AccessibleButton>
);
}
return (
<div className="mx_ServerOfflineDialog_content_context_txn" key={`txn-${j}`}>
<span className="mx_ServerOfflineDialog_content_context_txn_desc">
{ t.auditName }
</span>
{ button }
<span className="mx_ServerOfflineDialog_content_context_txn_desc">{t.auditName}</span>
{button}
</div>
);
});
return (
<div className="mx_ServerOfflineDialog_content_context" key={`context-${i}`}>
<div className="mx_ServerOfflineDialog_content_context_timestamp">
{ formatTime(c.firstFailedTime, SettingsStore.getValue("showTwelveHourTimestamps")) }
{formatTime(c.firstFailedTime, SettingsStore.getValue("showTwelveHourTimestamps"))}
</div>
<div className="mx_ServerOfflineDialog_content_context_timeline">
{ header }
{ entries }
{header}
{entries}
</div>
</div>
);
@ -89,37 +88,42 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
}
public render() {
let timeline = this.renderTimeline().filter(c => !!c); // remove nulls for next check
let timeline = this.renderTimeline().filter((c) => !!c); // remove nulls for next check
if (timeline.length === 0) {
timeline = [<div key={1}>{ _t("You're all caught up.") }</div>];
timeline = [<div key={1}>{_t("You're all caught up.")}</div>];
}
const serverName = MatrixClientPeg.getHomeserverName();
return <BaseDialog title={_t("Server isn't responding")}
className='mx_ServerOfflineDialog'
contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
hasCancel={true}
>
<div className="mx_ServerOfflineDialog_content">
<p>{ _t(
"Your server isn't responding to some of your requests. " +
"Below are some of the most likely reasons.",
) }</p>
<ul>
<li>{ _t("The server (%(serverName)s) took too long to respond.", { serverName }) }</li>
<li>{ _t("Your firewall or anti-virus is blocking the request.") }</li>
<li>{ _t("A browser extension is preventing the request.") }</li>
<li>{ _t("The server is offline.") }</li>
<li>{ _t("The server has denied your request.") }</li>
<li>{ _t("Your area is experiencing difficulties connecting to the internet.") }</li>
<li>{ _t("A connection error occurred while trying to contact the server.") }</li>
<li>{ _t("The server is not configured to indicate what the problem is (CORS).") }</li>
</ul>
<hr />
<h2>{ _t("Recent changes that have not yet been received") }</h2>
{ timeline }
</div>
</BaseDialog>;
return (
<BaseDialog
title={_t("Server isn't responding")}
className="mx_ServerOfflineDialog"
contentId="mx_Dialog_content"
onFinished={this.props.onFinished}
hasCancel={true}
>
<div className="mx_ServerOfflineDialog_content">
<p>
{_t(
"Your server isn't responding to some of your requests. " +
"Below are some of the most likely reasons.",
)}
</p>
<ul>
<li>{_t("The server (%(serverName)s) took too long to respond.", { serverName })}</li>
<li>{_t("Your firewall or anti-virus is blocking the request.")}</li>
<li>{_t("A browser extension is preventing the request.")}</li>
<li>{_t("The server is offline.")}</li>
<li>{_t("The server has denied your request.")}</li>
<li>{_t("Your area is experiencing difficulties connecting to the internet.")}</li>
<li>{_t("A connection error occurred while trying to contact the server.")}</li>
<li>{_t("The server is not configured to indicate what the problem is (CORS).")}</li>
</ul>
<hr />
<h2>{_t("Recent changes that have not yet been received")}</h2>
{timeline}
</div>
</BaseDialog>
);
}
}

View file

@ -19,8 +19,8 @@ import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
import { logger } from "matrix-js-sdk/src/logger";
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import BaseDialog from './BaseDialog';
import { _t } from '../../../languageHandler';
import BaseDialog from "./BaseDialog";
import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import SdkConfig from "../../../SdkConfig";
import Field from "../elements/Field";
@ -133,13 +133,14 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
key: "required",
test: ({ value, allowEmpty }) => allowEmpty || !!value,
invalid: () => _t("Specify a homeserver"),
}, {
},
{
key: "valid",
test: async function({ value }, { error }) {
test: async function ({ value }, { error }) {
if (!value) return true;
return !error;
},
invalid: function({ error }) {
invalid: function ({ error }) {
return error;
},
},
@ -172,70 +173,70 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
if (this.defaultServer.hsNameIsDifferent) {
defaultServerName = (
<TextWithTooltip class="mx_Login_underlinedServerName" tooltip={this.defaultServer.hsUrl}>
{ this.defaultServer.hsName }
{this.defaultServer.hsName}
</TextWithTooltip>
);
}
return <BaseDialog
title={this.props.title || _t("Sign into your homeserver")}
className="mx_ServerPickerDialog"
contentId="mx_ServerPickerDialog"
onFinished={this.props.onFinished}
fixedWidth={false}
hasCancel={true}
>
<form className="mx_Dialog_content" id="mx_ServerPickerDialog" onSubmit={this.onSubmit}>
<p>
{ _t("We call the places where you can host your account 'homeservers'.") } { text }
</p>
return (
<BaseDialog
title={this.props.title || _t("Sign into your homeserver")}
className="mx_ServerPickerDialog"
contentId="mx_ServerPickerDialog"
onFinished={this.props.onFinished}
fixedWidth={false}
hasCancel={true}
>
<form className="mx_Dialog_content" id="mx_ServerPickerDialog" onSubmit={this.onSubmit}>
<p>
{_t("We call the places where you can host your account 'homeservers'.")} {text}
</p>
<StyledRadioButton
name="defaultChosen"
value="true"
checked={this.state.defaultChosen}
onChange={this.onDefaultChosen}
>
{ defaultServerName }
</StyledRadioButton>
<StyledRadioButton
name="defaultChosen"
value="true"
checked={this.state.defaultChosen}
onChange={this.onDefaultChosen}
>
{defaultServerName}
</StyledRadioButton>
<StyledRadioButton
name="defaultChosen"
value="false"
className="mx_ServerPickerDialog_otherHomeserverRadio"
checked={!this.state.defaultChosen}
onChange={this.onOtherChosen}
childrenInLabel={false}
aria-label={_t("Other homeserver")}
>
<Field
type="text"
className="mx_ServerPickerDialog_otherHomeserver"
label={_t("Other homeserver")}
onChange={this.onHomeserverChange}
onFocus={this.onOtherChosen}
ref={this.fieldRef}
onValidate={this.onHomeserverValidate}
value={this.state.otherHomeserver}
validateOnChange={false}
validateOnFocus={false}
autoFocus={true}
id="mx_homeserverInput"
/>
</StyledRadioButton>
<p>
{ _t("Use your preferred Matrix homeserver if you have one, or host your own.") }
</p>
<StyledRadioButton
name="defaultChosen"
value="false"
className="mx_ServerPickerDialog_otherHomeserverRadio"
checked={!this.state.defaultChosen}
onChange={this.onOtherChosen}
childrenInLabel={false}
aria-label={_t("Other homeserver")}
>
<Field
type="text"
className="mx_ServerPickerDialog_otherHomeserver"
label={_t("Other homeserver")}
onChange={this.onHomeserverChange}
onFocus={this.onOtherChosen}
ref={this.fieldRef}
onValidate={this.onHomeserverValidate}
value={this.state.otherHomeserver}
validateOnChange={false}
validateOnFocus={false}
autoFocus={true}
id="mx_homeserverInput"
/>
</StyledRadioButton>
<p>{_t("Use your preferred Matrix homeserver if you have one, or host your own.")}</p>
<AccessibleButton className="mx_ServerPickerDialog_continue" kind="primary" onClick={this.onSubmit}>
{ _t("Continue") }
</AccessibleButton>
<AccessibleButton className="mx_ServerPickerDialog_continue" kind="primary" onClick={this.onSubmit}>
{_t("Continue")}
</AccessibleButton>
<h2>{ _t("Learn more") }</h2>
<a href="https://matrix.org/faq/#what-is-a-homeserver%3F" target="_blank" rel="noreferrer noopener">
{ _t("About homeservers") }
</a>
</form>
</BaseDialog>;
<h2>{_t("Learn more")}</h2>
<a href="https://matrix.org/faq/#what-is-a-homeserver%3F" target="_blank" rel="noreferrer noopener">
{_t("About homeservers")}
</a>
</form>
</BaseDialog>
);
}
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
@ -27,15 +27,17 @@ export default class SeshatResetDialog extends React.PureComponent<IDialogProps>
<BaseDialog
hasCancel={true}
onFinished={this.props.onFinished.bind(null, false)}
title={_t("Reset event store?")}>
title={_t("Reset event store?")}
>
<div>
<p>
{ _t("You most likely do not want to reset your event index store") }
{_t("You most likely do not want to reset your event index store")}
<br />
{ _t("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",
) }
{_t(
"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",
)}
</p>
</div>
<DialogButtons

View file

@ -16,11 +16,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig";
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import QuestionDialog from "./QuestionDialog";
import BugReportDialog from "./BugReportDialog";
import BaseDialog from "./BaseDialog";
@ -41,8 +41,7 @@ export default class SessionRestoreErrorDialog extends React.Component<IProps> {
private onClearStorageClick = (): void => {
Modal.createDialog(QuestionDialog, {
title: _t("Sign out"),
description:
<div>{ _t("Sign out and remove encryption keys?") }</div>,
description: <div>{_t("Sign out and remove encryption keys?")}</div>,
button: _t("Sign out"),
danger: true,
onFinished: this.props.onFinished,
@ -60,53 +59,63 @@ export default class SessionRestoreErrorDialog extends React.Component<IProps> {
const clearStorageButton = (
<button onClick={this.onClearStorageClick} className="danger">
{ _t("Clear Storage and Sign Out") }
{_t("Clear Storage and Sign Out")}
</button>
);
let dialogButtons;
if (SdkConfig.get().bug_report_endpoint_url) {
dialogButtons = <DialogButtons primaryButton={_t("Send Logs")}
onPrimaryButtonClick={this.sendBugReport}
focus={true}
hasCancel={false}
>
{ clearStorageButton }
</DialogButtons>;
dialogButtons = (
<DialogButtons
primaryButton={_t("Send Logs")}
onPrimaryButtonClick={this.sendBugReport}
focus={true}
hasCancel={false}
>
{clearStorageButton}
</DialogButtons>
);
} else {
dialogButtons = <DialogButtons primaryButton={_t("Refresh")}
onPrimaryButtonClick={this.onRefreshClick}
focus={true}
hasCancel={false}
>
{ clearStorageButton }
</DialogButtons>;
dialogButtons = (
<DialogButtons
primaryButton={_t("Refresh")}
onPrimaryButtonClick={this.onRefreshClick}
focus={true}
hasCancel={false}
>
{clearStorageButton}
</DialogButtons>
);
}
return (
<BaseDialog
className="mx_ErrorDialog"
onFinished={this.props.onFinished}
title={_t('Unable to restore session')}
contentId='mx_Dialog_content'
title={_t("Unable to restore session")}
contentId="mx_Dialog_content"
hasCancel={false}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<p>{ _t("We encountered an error trying to restore your previous session.") }</p>
<div className="mx_Dialog_content" id="mx_Dialog_content">
<p>{_t("We encountered an error trying to restore your previous session.")}</p>
<p>{ _t(
"If you have previously used a more recent version of %(brand)s, your session " +
"may be incompatible with this version. Close this window and return " +
"to the more recent version.",
{ brand },
) }</p>
<p>
{_t(
"If you have previously used a more recent version of %(brand)s, your session " +
"may be incompatible with this version. Close this window and return " +
"to the more recent version.",
{ brand },
)}
</p>
<p>{ _t(
"Clearing your browser's storage may fix the problem, but will sign you " +
"out and cause any encrypted chat history to become unreadable.",
) }</p>
<p>
{_t(
"Clearing your browser's storage may fix the problem, but will sign you " +
"out and cause any encrypted chat history to become unreadable.",
)}
</p>
</div>
{ dialogButtons }
{dialogButtons}
</BaseDialog>
);
}

View file

@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { logger } from "matrix-js-sdk/src/logger";
import * as Email from '../../../email';
import AddThreepid from '../../../AddThreepid';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import * as Email from "../../../email";
import AddThreepid from "../../../AddThreepid";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import Spinner from "../elements/Spinner";
import ErrorDialog from "./ErrorDialog";
import QuestionDialog from "./QuestionDialog";
@ -50,7 +50,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
super(props);
this.state = {
emailAddress: '',
emailAddress: "",
emailBusy: false,
};
}
@ -71,24 +71,27 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
return;
}
this.addThreepid = new AddThreepid();
this.addThreepid.addEmailAddress(emailAddress).then(() => {
Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"),
description: _t(
"Please check your email and click on the link it contains. Once this " +
"is done, click continue.",
),
button: _t('Continue'),
onFinished: this.onEmailDialogFinished,
});
}, (err) => {
this.setState({ emailBusy: false });
logger.error("Unable to add email address " + emailAddress + " " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to add email address"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
});
this.addThreepid.addEmailAddress(emailAddress).then(
() => {
Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"),
description: _t(
"Please check your email and click on the link it contains. Once this " +
"is done, click continue.",
),
button: _t("Continue"),
onFinished: this.onEmailDialogFinished,
});
},
(err) => {
this.setState({ emailBusy: false });
logger.error("Unable to add email address " + emailAddress + " " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to add email address"),
description: err && err.message ? err.message : _t("Operation failed"),
});
},
);
this.setState({ emailBusy: true });
};
@ -105,61 +108,66 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
};
private verifyEmailAddress(): void {
this.addThreepid.checkEmailLinkClicked().then(() => {
this.props.onFinished(true);
}, (err) => {
this.setState({ emailBusy: false });
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
const message = _t("Unable to verify email address.") + " " +
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"),
description: message,
button: _t('Continue'),
onFinished: this.onEmailDialogFinished,
});
} else {
logger.error("Unable to verify email address: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify email address."),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
}
});
this.addThreepid.checkEmailLinkClicked().then(
() => {
this.props.onFinished(true);
},
(err) => {
this.setState({ emailBusy: false });
if (err.errcode == "M_THREEPID_AUTH_FAILED") {
const message =
_t("Unable to verify email address.") +
" " +
_t(
"Please check your email and click on the link it contains. Once this is done, click continue.",
);
Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"),
description: message,
button: _t("Continue"),
onFinished: this.onEmailDialogFinished,
});
} else {
logger.error("Unable to verify email address: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify email address."),
description: err && err.message ? err.message : _t("Operation failed"),
});
}
},
);
}
public render(): JSX.Element {
const emailInput = this.state.emailBusy ? <Spinner /> : <EditableText
initialValue={this.state.emailAddress}
className="mx_SetEmailDialog_email_input"
placeholder={_t("Email address")}
placeholderClassName="mx_SetEmailDialog_email_input_placeholder"
blurToCancel={false}
onValueChanged={this.onEmailAddressChanged} />;
const emailInput = this.state.emailBusy ? (
<Spinner />
) : (
<EditableText
initialValue={this.state.emailAddress}
className="mx_SetEmailDialog_email_input"
placeholder={_t("Email address")}
placeholderClassName="mx_SetEmailDialog_email_input_placeholder"
blurToCancel={false}
onValueChanged={this.onEmailAddressChanged}
/>
);
return (
<BaseDialog className="mx_SetEmailDialog"
<BaseDialog
className="mx_SetEmailDialog"
onFinished={this.onCancelled}
title={this.props.title}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
>
<div className="mx_Dialog_content">
<p id='mx_Dialog_content'>
{ _t('This will allow you to reset your password and receive notifications.') }
<p id="mx_Dialog_content">
{_t("This will allow you to reset your password and receive notifications.")}
</p>
{ emailInput }
{emailInput}
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary"
type="submit"
value={_t("Continue")}
onClick={this.onSubmit}
/>
<input
type="submit"
value={_t("Skip")}
onClick={this.onCancelled}
/>
<input className="mx_Dialog_primary" type="submit" value={_t("Continue")} onClick={this.onSubmit} />
<input type="submit" value={_t("Skip")} onClick={this.onCancelled} />
</div>
</BaseDialog>
);

View file

@ -15,17 +15,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as React from 'react';
import * as React from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { User } from "matrix-js-sdk/src/models/user";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import QRCode from "../elements/QRCode";
import { RoomPermalinkCreator, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
import { selectText } from "../../../utils/strings";
import StyledCheckbox from '../elements/StyledCheckbox';
import StyledCheckbox from "../elements/StyledCheckbox";
import { IDialogProps } from "./IDialogProps";
import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
@ -34,27 +34,31 @@ import CopyableText from "../elements/CopyableText";
const socials = [
{
name: 'Facebook',
name: "Facebook",
img: require("../../../../res/img/social/facebook.png"),
url: (url) => `https://www.facebook.com/sharer/sharer.php?u=${url}`,
}, {
name: 'Twitter',
},
{
name: "Twitter",
img: require("../../../../res/img/social/twitter-2.png"),
url: (url) => `https://twitter.com/home?status=${url}`,
}, /* // icon missing
},
/* // icon missing
name: 'Google Plus',
img: 'img/social/',
url: (url) => `https://plus.google.com/share?url=${url}`,
},*/ {
name: 'LinkedIn',
name: "LinkedIn",
img: require("../../../../res/img/social/linkedin.png"),
url: (url) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`,
}, {
name: 'Reddit',
},
{
name: "Reddit",
img: require("../../../../res/img/social/reddit.png"),
url: (url) => `https://www.reddit.com/submit?url=${url}`,
}, {
name: 'email',
},
{
name: "email",
img: require("../../../../res/img/social/email-1.png"),
url: (url) => `mailto:?body=${url}`,
},
@ -125,31 +129,35 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
let checkbox;
if (this.props.target instanceof Room) {
title = _t('Share Room');
title = _t("Share Room");
const events = this.props.target.getLiveTimeline().getEvents();
if (events.length > 0) {
checkbox = <div>
checkbox = (
<div>
<StyledCheckbox
checked={this.state.linkSpecificEvent}
onChange={this.onLinkSpecificEventCheckboxClick}
>
{_t("Link to most recent message")}
</StyledCheckbox>
</div>
);
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
title = _t("Share User");
} else if (this.props.target instanceof MatrixEvent) {
title = _t("Share Room Message");
checkbox = (
<div>
<StyledCheckbox
checked={this.state.linkSpecificEvent}
onChange={this.onLinkSpecificEventCheckboxClick}
>
{ _t('Link to most recent message') }
{_t("Link to selected message")}
</StyledCheckbox>
</div>;
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
title = _t('Share User');
} else if (this.props.target instanceof MatrixEvent) {
title = _t('Share Room Message');
checkbox = <div>
<StyledCheckbox
checked={this.state.linkSpecificEvent}
onChange={this.onLinkSpecificEventCheckboxClick}
>
{ _t('Link to selected message') }
</StyledCheckbox>
</div>;
</div>
);
}
const matrixToUrl = this.getUrl();
@ -160,45 +168,53 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
let qrSocialSection;
if (showQrCode || showSocials) {
qrSocialSection = <>
<hr />
<div className="mx_ShareDialog_split">
{ showQrCode && <div className="mx_ShareDialog_qrcode_container">
<QRCode data={matrixToUrl} width={256} />
</div> }
{ showSocials && <div className="mx_ShareDialog_social_container">
{ socials.map((social) => (
<a
rel="noreferrer noopener"
target="_blank"
key={social.name}
title={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
>
<img src={social.img} alt={social.name} height={64} width={64} />
</a>
)) }
</div> }
</div>
</>;
qrSocialSection = (
<>
<hr />
<div className="mx_ShareDialog_split">
{showQrCode && (
<div className="mx_ShareDialog_qrcode_container">
<QRCode data={matrixToUrl} width={256} />
</div>
)}
{showSocials && (
<div className="mx_ShareDialog_social_container">
{socials.map((social) => (
<a
rel="noreferrer noopener"
target="_blank"
key={social.name}
title={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
>
<img src={social.img} alt={social.name} height={64} width={64} />
</a>
))}
</div>
)}
</div>
</>
);
}
return <BaseDialog
title={title}
className='mx_ShareDialog'
contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
>
<div className="mx_ShareDialog_content">
<CopyableText getTextToCopy={() => matrixToUrl}>
<a title={_t('Link to room')} href={matrixToUrl} onClick={ShareDialog.onLinkClick}>
{ matrixToUrl }
</a>
</CopyableText>
{ checkbox }
{ qrSocialSection }
</div>
</BaseDialog>;
return (
<BaseDialog
title={title}
className="mx_ShareDialog"
contentId="mx_Dialog_content"
onFinished={this.props.onFinished}
>
<div className="mx_ShareDialog_content">
<CopyableText getTextToCopy={() => matrixToUrl}>
<a title={_t("Link to room")} href={matrixToUrl} onClick={ShareDialog.onLinkClick}>
{matrixToUrl}
</a>
</CopyableText>
{checkbox}
{qrSocialSection}
</div>
</BaseDialog>
);
}
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from "../../../languageHandler";
import { CommandCategories, Commands } from "../../../SlashCommands";
@ -25,7 +25,7 @@ interface IProps extends IDialogProps {}
const SlashCommandHelpDialog: React.FC<IProps> = ({ onFinished }) => {
const categories = {};
Commands.forEach(cmd => {
Commands.forEach((cmd) => {
if (!cmd.isEnabled()) return;
if (!categories[cmd.category]) {
categories[cmd.category] = [];
@ -33,36 +33,45 @@ const SlashCommandHelpDialog: React.FC<IProps> = ({ onFinished }) => {
categories[cmd.category].push(cmd);
});
const body = Object.values(CommandCategories).filter(c => categories[c]).map((category) => {
const rows = [
<tr key={"_category_" + category} className="mx_SlashCommandHelpDialog_headerRow">
<td colSpan={3}>
<h2>{ _t(category) }</h2>
</td>
</tr>,
];
const body = Object.values(CommandCategories)
.filter((c) => categories[c])
.map((category) => {
const rows = [
<tr key={"_category_" + category} className="mx_SlashCommandHelpDialog_headerRow">
<td colSpan={3}>
<h2>{_t(category)}</h2>
</td>
</tr>,
];
categories[category].forEach(cmd => {
rows.push(<tr key={cmd.command}>
<td><strong>{ cmd.getCommand() }</strong></td>
<td>{ cmd.args }</td>
<td>{ cmd.description }</td>
</tr>);
categories[category].forEach((cmd) => {
rows.push(
<tr key={cmd.command}>
<td>
<strong>{cmd.getCommand()}</strong>
</td>
<td>{cmd.args}</td>
<td>{cmd.description}</td>
</tr>,
);
});
return rows;
});
return rows;
});
return <InfoDialog
className="mx_SlashCommandHelpDialog"
title={_t("Command Help")}
description={<table>
<tbody>
{ body }
</tbody>
</table>}
hasCloseButton={true}
onFinished={onFinished} />;
return (
<InfoDialog
className="mx_SlashCommandHelpDialog"
title={_t("Command Help")}
description={
<table>
<tbody>{body}</tbody>
</table>
}
hasCloseButton={true}
onFinished={onFinished}
/>
);
};
export default SlashCommandHelpDialog;

View file

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { MatrixClient, Method } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';
import React from "react";
import { MatrixClient, Method } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import SettingsStore from "../../../settings/SettingsStore";
import TextInputDialog from "./TextInputDialog";
@ -66,7 +66,15 @@ async function proxyHealthCheck(endpoint: string, hsUrl?: string): Promise<void>
export const SlidingSyncOptionsDialog: React.FC<IDialogProps> = ({ onFinished }) => {
const cli = MatrixClientPeg.get();
const currentProxy = SettingsStore.getValue("feature_sliding_sync_proxy_url");
const hasNativeSupport = useAsyncMemo(() => syncHealthCheck(cli).then(() => true, () => false), [], null);
const hasNativeSupport = useAsyncMemo(
() =>
syncHealthCheck(cli).then(
() => true,
() => false,
),
[],
null,
);
let nativeSupport: string;
if (hasNativeSupport === null) {
@ -91,7 +99,8 @@ export const SlidingSyncOptionsDialog: React.FC<IDialogProps> = ({ onFinished })
key: "required",
test: async ({ value }) => !!value || hasNativeSupport,
invalid: () => _t("Your server lacks native support, you must specify a proxy"),
}, {
},
{
key: "working",
final: true,
test: async (_, { error }) => !error,
@ -101,23 +110,29 @@ export const SlidingSyncOptionsDialog: React.FC<IDialogProps> = ({ onFinished })
],
});
return <TextInputDialog
title={_t("Sliding Sync configuration")}
description={<div>
<div><b>{ _t("To disable you will need to log out and back in, use with caution!") }</b></div>
{ nativeSupport }
</div>}
placeholder={hasNativeSupport ? _t('Proxy URL (optional)') : _t('Proxy URL')}
value={currentProxy}
button={_t("Enable")}
validator={validProxy}
onFinished={(enable: boolean, proxyUrl: string) => {
if (enable) {
SettingsStore.setValue("feature_sliding_sync_proxy_url", null, SettingLevel.DEVICE, proxyUrl);
onFinished(true);
} else {
onFinished(false);
return (
<TextInputDialog
title={_t("Sliding Sync configuration")}
description={
<div>
<div>
<b>{_t("To disable you will need to log out and back in, use with caution!")}</b>
</div>
{nativeSupport}
</div>
}
}}
/>;
placeholder={hasNativeSupport ? _t("Proxy URL (optional)") : _t("Proxy URL")}
value={currentProxy}
button={_t("Enable")}
validator={validProxy}
onFinished={(enable: boolean, proxyUrl: string) => {
if (enable) {
SettingsStore.setValue("feature_sliding_sync_proxy_url", null, SettingLevel.DEVICE, proxyUrl);
onFinished(true);
} else {
onFinished(false);
}
}}
/>
);
};

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, { ChangeEvent } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t, _td } from '../../../languageHandler';
import { _t, _td } from "../../../languageHandler";
import BaseDialog from "../dialogs/BaseDialog";
import { IDialogProps } from "./IDialogProps";
import TabbedView, { Tab } from "../../structures/TabbedView";
@ -38,7 +38,7 @@ const SpacePreferencesAppearanceTab = ({ space }: Pick<IProps, "space">) => {
return (
<div className="mx_SettingsTab">
<div className="mx_SettingsTab_heading">{ _t("Sections to show") }</div>
<div className="mx_SettingsTab_heading">{_t("Sections to show")}</div>
<div className="mx_SettingsTab_section">
<StyledCheckbox
@ -52,13 +52,16 @@ const SpacePreferencesAppearanceTab = ({ space }: Pick<IProps, "space">) => {
);
}}
>
{ _t("People") }
{_t("People")}
</StyledCheckbox>
<p>
{ _t("This groups your chats with members of this space. " +
"Turning this off will hide those chats from your view of %(spaceName)s.", {
spaceName: space.name,
}) }
{_t(
"This groups your chats with members of this space. " +
"Turning this off will hide those chats from your view of %(spaceName)s.",
{
spaceName: space.name,
},
)}
</p>
</div>
</div>

View file

@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useMemo } from 'react';
import React, { useMemo } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { _t, _td } from '../../../languageHandler';
import { _t, _td } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { useDispatcher } from "../../../hooks/useDispatcher";
import TabbedView, { Tab } from "../../structures/TabbedView";
import SpaceSettingsGeneralTab from '../spaces/SpaceSettingsGeneralTab';
import SpaceSettingsGeneralTab from "../spaces/SpaceSettingsGeneralTab";
import SpaceSettingsVisibilityTab from "../spaces/SpaceSettingsVisibilityTab";
import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
import { Action } from '../../../dispatcher/actions';
import { Action } from "../../../dispatcher/actions";
export enum SpaceSettingsTab {
General = "SPACE_GENERAL_TAB",
@ -73,30 +73,32 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
),
SettingsStore.getValue(UIFeature.AdvancedSettings)
? new Tab(
SpaceSettingsTab.Advanced,
_td("Advanced"),
"mx_RoomSettingsDialog_warningIcon",
<AdvancedRoomSettingsTab roomId={space.roomId} closeSettingsFn={onFinished} />,
)
SpaceSettingsTab.Advanced,
_td("Advanced"),
"mx_RoomSettingsDialog_warningIcon",
<AdvancedRoomSettingsTab roomId={space.roomId} closeSettingsFn={onFinished} />,
)
: null,
].filter(Boolean);
}, [cli, space, onFinished]);
return <BaseDialog
title={_t("Space settings")}
className="mx_SpaceSettingsDialog"
contentId="mx_SpaceSettingsDialog"
onFinished={onFinished}
fixedWidth={false}
>
<div
className="mx_SpaceSettingsDialog_content"
id="mx_SpaceSettingsDialog"
title={_t("Settings - %(spaceName)s", { spaceName: space.name })}
return (
<BaseDialog
title={_t("Space settings")}
className="mx_SpaceSettingsDialog"
contentId="mx_SpaceSettingsDialog"
onFinished={onFinished}
fixedWidth={false}
>
<TabbedView tabs={tabs} />
</div>
</BaseDialog>;
<div
className="mx_SpaceSettingsDialog_content"
id="mx_SpaceSettingsDialog"
title={_t("Settings - %(spaceName)s", { spaceName: space.name })}
>
<TabbedView tabs={tabs} />
</div>
</BaseDialog>
);
};
export default SpaceSettingsDialog;

View file

@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig";
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import BugReportDialog from "./BugReportDialog";
import { IDialogProps } from "./IDialogProps";
import AccessibleButton from '../elements/AccessibleButton';
import AccessibleButton from "../elements/AccessibleButton";
interface IProps extends IDialogProps { }
interface IProps extends IDialogProps {}
export default class StorageEvictedDialog extends React.Component<IProps> {
private sendBugReport = (ev: React.MouseEvent): void => {
@ -44,9 +44,11 @@ export default class StorageEvictedDialog extends React.Component<IProps> {
"To help us prevent this in future, please <a>send us logs</a>.",
{},
{
a: text => <AccessibleButton kind='link_inline' onClick={this.sendBugReport}>
{ text }
</AccessibleButton>,
a: (text) => (
<AccessibleButton kind="link_inline" onClick={this.sendBugReport}>
{text}
</AccessibleButton>
),
},
);
}
@ -55,22 +57,24 @@ export default class StorageEvictedDialog extends React.Component<IProps> {
<BaseDialog
className="mx_ErrorDialog"
onFinished={this.props.onFinished}
title={_t('Missing session data')}
contentId='mx_Dialog_content'
title={_t("Missing session data")}
contentId="mx_Dialog_content"
hasCancel={false}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<p>{ _t(
"Some session data, including encrypted message keys, is " +
"missing. Sign out and sign in to fix this, restoring keys " +
"from backup.",
) }</p>
<p>{ _t(
"Your browser likely removed this data when running low on " +
"disk space.",
) } { logRequest }</p>
<div className="mx_Dialog_content" id="mx_Dialog_content">
<p>
{_t(
"Some session data, including encrypted message keys, is " +
"missing. Sign out and sign in to fix this, restoring keys " +
"from backup.",
)}
</p>
<p>
{_t("Your browser likely removed this data when running low on " + "disk space.")} {logRequest}
</p>
</div>
<DialogButtons primaryButton={_t("Sign out")}
<DialogButtons
primaryButton={_t("Sign out")}
onPrimaryButtonClick={this.onSignOutClick}
focus={true}
hasCancel={false}

View file

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import url from 'url';
import React from 'react';
import url from "url";
import React from "react";
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
import { _t, pickBestLanguage } from '../../../languageHandler';
import { _t, pickBestLanguage } from "../../../languageHandler";
import DialogButtons from "../elements/DialogButtons";
import BaseDialog from "./BaseDialog";
@ -34,10 +34,7 @@ class TermsCheckbox extends React.PureComponent<ITermsCheckboxProps> {
};
render() {
return <input type="checkbox"
onChange={this.onChange}
checked={this.props.checked}
/>;
return <input type="checkbox" onChange={this.onChange} checked={this.props.checked} />;
}
}
@ -82,30 +79,43 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
};
private onNextClick = (): void => {
this.props.onFinished(true, Object.keys(this.state.agreedUrls).filter((url) => this.state.agreedUrls[url]));
this.props.onFinished(
true,
Object.keys(this.state.agreedUrls).filter((url) => this.state.agreedUrls[url]),
);
};
private nameForServiceType(serviceType: SERVICE_TYPES, host: string): JSX.Element {
switch (serviceType) {
case SERVICE_TYPES.IS:
return <div>{ _t("Identity server") }<br />({ host })</div>;
return (
<div>
{_t("Identity server")}
<br />({host})
</div>
);
case SERVICE_TYPES.IM:
return <div>{ _t("Integration manager") }<br />({ host })</div>;
return (
<div>
{_t("Integration manager")}
<br />({host})
</div>
);
}
}
private summaryForServiceType(serviceType: SERVICE_TYPES): JSX.Element {
switch (serviceType) {
case SERVICE_TYPES.IS:
return <div>
{ _t("Find others by phone or email") }
<br />
{ _t("Be found by phone or email") }
</div>;
return (
<div>
{_t("Find others by phone or email")}
<br />
{_t("Be found by phone or email")}
</div>
);
case SERVICE_TYPES.IM:
return <div>
{ _t("Use bots, bridges, widgets and sticker packs") }
</div>;
return <div>{_t("Use bots, bridges, widgets and sticker packs")}</div>;
}
}
@ -123,31 +133,33 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
const policyValues = Object.values(policiesAndService.policies);
for (let i = 0; i < policyValues.length; ++i) {
const termDoc = policyValues[i];
const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== 'version'));
const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version"));
let serviceName;
let summary;
if (i === 0) {
serviceName = this.nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host);
summary = this.summaryForServiceType(
policiesAndService.service.serviceType,
);
summary = this.summaryForServiceType(policiesAndService.service.serviceType);
}
rows.push(<tr key={termDoc[termsLang].url}>
<td className="mx_TermsDialog_service">{ serviceName }</td>
<td className="mx_TermsDialog_summary">{ summary }</td>
<td>
{ termDoc[termsLang].name }
<a rel="noreferrer noopener" target="_blank" href={termDoc[termsLang].url}>
<span className="mx_TermsDialog_link" />
</a>
</td>
<td><TermsCheckbox
url={termDoc[termsLang].url}
onChange={this.onTermsCheckboxChange}
checked={Boolean(this.state.agreedUrls[termDoc[termsLang].url])}
/></td>
</tr>);
rows.push(
<tr key={termDoc[termsLang].url}>
<td className="mx_TermsDialog_service">{serviceName}</td>
<td className="mx_TermsDialog_summary">{summary}</td>
<td>
{termDoc[termsLang].name}
<a rel="noreferrer noopener" target="_blank" href={termDoc[termsLang].url}>
<span className="mx_TermsDialog_link" />
</a>
</td>
<td>
<TermsCheckbox
url={termDoc[termsLang].url}
onChange={this.onTermsCheckboxChange}
checked={Boolean(this.state.agreedUrls[termDoc[termsLang].url])}
/>
</td>
</tr>,
);
}
}
@ -159,7 +171,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
for (const terms of Object.values(policiesAndService.policies)) {
let docAgreed = false;
for (const lang of Object.keys(terms)) {
if (lang === 'version') continue;
if (lang === "version") continue;
if (this.state.agreedUrls[terms[lang].url]) {
docAgreed = true;
break;
@ -180,24 +192,27 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
fixedWidth={false}
onFinished={this.onCancelClick}
title={_t("Terms of Service")}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
hasCancel={false}
>
<div id='mx_Dialog_content'>
<p>{ _t("To continue you need to accept the terms of this service.") }</p>
<div id="mx_Dialog_content">
<p>{_t("To continue you need to accept the terms of this service.")}</p>
<table className="mx_TermsDialog_termsTable"><tbody>
<tr className="mx_TermsDialog_termsTableHeader">
<th>{ _t("Service") }</th>
<th>{ _t("Summary") }</th>
<th>{ _t("Document") }</th>
<th>{ _t("Accept") }</th>
</tr>
{ rows }
</tbody></table>
<table className="mx_TermsDialog_termsTable">
<tbody>
<tr className="mx_TermsDialog_termsTableHeader">
<th>{_t("Service")}</th>
<th>{_t("Summary")}</th>
<th>{_t("Document")}</th>
<th>{_t("Accept")}</th>
</tr>
{rows}
</tbody>
</table>
</div>
<DialogButtons primaryButton={_t('Next')}
<DialogButtons
primaryButton={_t("Next")}
hasCancel={true}
onCancel={this.onCancelClick}
onPrimaryButtonClick={this.onNextClick}

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ChangeEvent, createRef } from 'react';
import React, { ChangeEvent, createRef } from "react";
import Field from "../elements/Field";
import { _t, _td } from '../../../languageHandler';
import { _t, _td } from "../../../languageHandler";
import { IFieldState, IValidationResult } from "../elements/Validation";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
@ -117,7 +117,7 @@ export default class TextInputDialog extends React.Component<IProps, IState> {
<form onSubmit={this.onOk}>
<div className="mx_Dialog_content">
<div className="mx_TextInputDialog_label">
<label htmlFor="textinput"> { this.props.description } </label>
<label htmlFor="textinput"> {this.props.description} </label>
</div>
<div>
<Field

View file

@ -38,36 +38,44 @@ const UntrustedDeviceDialog: React.FC<IProps> = ({ device, user, onFinished }) =
newSessionText = _t("You signed in to a new session without verifying it:");
askToVerifyText = _t("Verify your other session using one of the options below.");
} else {
newSessionText = _t("%(name)s (%(userId)s) signed in to a new session without verifying it:",
{ name: user.displayName, userId: user.userId });
newSessionText = _t("%(name)s (%(userId)s) signed in to a new session without verifying it:", {
name: user.displayName,
userId: user.userId,
});
askToVerifyText = _t("Ask this user to verify their session, or manually verify it below.");
}
return <BaseDialog
onFinished={onFinished}
className="mx_UntrustedDeviceDialog"
title={<>
<E2EIcon status={E2EState.Warning} size={24} hideTooltip={true} />
{ _t("Not Trusted") }
</>}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<p>{ newSessionText }</p>
<p>{ device.getDisplayName() } ({ device.deviceId })</p>
<p>{ askToVerifyText }</p>
</div>
<div className='mx_Dialog_buttons'>
<AccessibleButton kind="primary_outline" onClick={() => onFinished("legacy")}>
{ _t("Manually verify by text") }
</AccessibleButton>
<AccessibleButton kind="primary_outline" onClick={() => onFinished("sas")}>
{ _t("Interactively verify by emoji") }
</AccessibleButton>
<AccessibleButton kind="primary" onClick={() => onFinished(false)}>
{ _t("Done") }
</AccessibleButton>
</div>
</BaseDialog>;
return (
<BaseDialog
onFinished={onFinished}
className="mx_UntrustedDeviceDialog"
title={
<>
<E2EIcon status={E2EState.Warning} size={24} hideTooltip={true} />
{_t("Not Trusted")}
</>
}
>
<div className="mx_Dialog_content" id="mx_Dialog_content">
<p>{newSessionText}</p>
<p>
{device.getDisplayName()} ({device.deviceId})
</p>
<p>{askToVerifyText}</p>
</div>
<div className="mx_Dialog_buttons">
<AccessibleButton kind="primary_outline" onClick={() => onFinished("legacy")}>
{_t("Manually verify by text")}
</AccessibleButton>
<AccessibleButton kind="primary_outline" onClick={() => onFinished("sas")}>
{_t("Interactively verify by emoji")}
</AccessibleButton>
<AccessibleButton kind="primary" onClick={() => onFinished(false)}>
{_t("Done")}
</AccessibleButton>
</div>
</BaseDialog>
);
};
export default UntrustedDeviceDialog;

View file

@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { filesize } from "filesize";
import { Icon as FileIcon } from '../../../../res/img/feather-customised/files.svg';
import { _t } from '../../../languageHandler';
import { getBlobSafeMimeType } from '../../../utils/blobs';
import { Icon as FileIcon } from "../../../../res/img/feather-customised/files.svg";
import { _t } from "../../../languageHandler";
import { getBlobSafeMimeType } from "../../../utils/blobs";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
@ -45,9 +45,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
// Create a fresh `Blob` for previewing (even though `File` already is
// one) so we can adjust the MIME type if needed.
this.mimeType = getBlobSafeMimeType(props.file.type);
const blob = new Blob([props.file], { type:
this.mimeType,
});
const blob = new Blob([props.file], { type: this.mimeType });
this.objectUrl = URL.createObjectURL(blob);
}
@ -70,15 +68,12 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
render() {
let title: string;
if (this.props.totalFiles > 1 && this.props.currentIndex !== undefined) {
title = _t(
"Upload files (%(current)s of %(total)s)",
{
current: this.props.currentIndex + 1,
total: this.props.totalFiles,
},
);
title = _t("Upload files (%(current)s of %(total)s)", {
current: this.props.currentIndex + 1,
total: this.props.totalFiles,
});
} else {
title = _t('Upload files');
title = _t("Upload files");
}
const fileId = `mx-uploadconfirmdialog-${this.props.file.name}`;
@ -86,31 +81,24 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
let placeholder: JSX.Element;
if (this.mimeType.startsWith("image/")) {
preview = (
<img
className="mx_UploadConfirmDialog_imagePreview"
src={this.objectUrl}
aria-labelledby={fileId}
/>
<img className="mx_UploadConfirmDialog_imagePreview" src={this.objectUrl} aria-labelledby={fileId} />
);
} else if (this.mimeType.startsWith("video/")) {
preview = (
<video className="mx_UploadConfirmDialog_imagePreview" src={this.objectUrl} playsInline controls={false} />
);
} else {
placeholder = (
<FileIcon
className="mx_UploadConfirmDialog_fileIcon"
height={18}
width={18}
<video
className="mx_UploadConfirmDialog_imagePreview"
src={this.objectUrl}
playsInline
controls={false}
/>
);
} else {
placeholder = <FileIcon className="mx_UploadConfirmDialog_fileIcon" height={18} width={18} />;
}
let uploadAllButton;
if (this.props.currentIndex + 1 < this.props.totalFiles) {
uploadAllButton = <button onClick={this.onUploadAllClick}>
{ _t("Upload all") }
</button>;
uploadAllButton = <button onClick={this.onUploadAllClick}>{_t("Upload all")}</button>;
}
return (
@ -124,21 +112,22 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
<div id="mx_Dialog_content">
<div className="mx_UploadConfirmDialog_previewOuter">
<div className="mx_UploadConfirmDialog_previewInner">
{ preview && <div>{ preview }</div> }
{preview && <div>{preview}</div>}
<div id={fileId}>
{ placeholder }
{ this.props.file.name } ({ filesize(this.props.file.size) })
{placeholder}
{this.props.file.name} ({filesize(this.props.file.size)})
</div>
</div>
</div>
</div>
<DialogButtons primaryButton={_t('Upload')}
<DialogButtons
primaryButton={_t("Upload")}
hasCancel={false}
onPrimaryButtonClick={this.onUploadClick}
focus={true}
>
{ uploadAllButton }
{uploadAllButton}
</DialogButtons>
</BaseDialog>
);

View file

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { filesize } from 'filesize';
import React from 'react';
import { filesize } from "filesize";
import React from "react";
import { _t } from '../../../languageHandler';
import ContentMessages from '../../../ContentMessages';
import { _t } from "../../../languageHandler";
import ContentMessages from "../../../ContentMessages";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import { IDialogProps } from "./IDialogProps";
@ -50,67 +50,77 @@ export default class UploadFailureDialog extends React.Component<IProps> {
if (this.props.totalFiles === 1 && this.props.badFiles.length === 1) {
message = _t(
"This file is <b>too large</b> to upload. " +
"The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.",
"The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.",
{
limit: filesize(this.props.contentMessages.getUploadLimit()),
sizeOfThisFile: filesize(this.props.badFiles[0].size),
}, {
b: sub => <b>{ sub }</b>,
},
{
b: (sub) => <b>{sub}</b>,
},
);
buttons = <DialogButtons primaryButton={_t('OK')}
hasCancel={false}
onPrimaryButtonClick={this.onCancelClick}
focus={true}
/>;
buttons = (
<DialogButtons
primaryButton={_t("OK")}
hasCancel={false}
onPrimaryButtonClick={this.onCancelClick}
focus={true}
/>
);
} else if (this.props.totalFiles === this.props.badFiles.length) {
message = _t(
"These files are <b>too large</b> to upload. " +
"The file size limit is %(limit)s.",
"These files are <b>too large</b> to upload. " + "The file size limit is %(limit)s.",
{
limit: filesize(this.props.contentMessages.getUploadLimit()),
}, {
b: sub => <b>{ sub }</b>,
},
{
b: (sub) => <b>{sub}</b>,
},
);
buttons = <DialogButtons primaryButton={_t('OK')}
hasCancel={false}
onPrimaryButtonClick={this.onCancelClick}
focus={true}
/>;
buttons = (
<DialogButtons
primaryButton={_t("OK")}
hasCancel={false}
onPrimaryButtonClick={this.onCancelClick}
focus={true}
/>
);
} else {
message = _t(
"Some files are <b>too large</b> to be uploaded. " +
"The file size limit is %(limit)s.",
"Some files are <b>too large</b> to be uploaded. " + "The file size limit is %(limit)s.",
{
limit: filesize(this.props.contentMessages.getUploadLimit()),
}, {
b: sub => <b>{ sub }</b>,
},
{
b: (sub) => <b>{sub}</b>,
},
);
const howManyOthers = this.props.totalFiles - this.props.badFiles.length;
buttons = <DialogButtons
primaryButton={_t('Upload %(count)s other files', { count: howManyOthers })}
onPrimaryButtonClick={this.onUploadClick}
hasCancel={true}
cancelButton={_t("Cancel All")}
onCancel={this.onCancelClick}
focus={true}
/>;
buttons = (
<DialogButtons
primaryButton={_t("Upload %(count)s other files", { count: howManyOthers })}
onPrimaryButtonClick={this.onUploadClick}
hasCancel={true}
cancelButton={_t("Cancel All")}
onCancel={this.onCancelClick}
focus={true}
/>
);
}
return (
<BaseDialog className='mx_UploadFailureDialog'
<BaseDialog
className="mx_UploadFailureDialog"
onFinished={this.onCancelClick}
title={_t("Upload Error")}
contentId='mx_Dialog_content'
contentId="mx_Dialog_content"
>
<div id='mx_Dialog_content'>
{ message }
{ preview }
<div id="mx_Dialog_content">
{message}
{preview}
</div>
{ buttons }
{buttons}
</BaseDialog>
);
}

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import TabbedView, { Tab } from "../../structures/TabbedView";
import { _t, _td } from "../../../languageHandler";
@ -35,7 +35,7 @@ import BaseDialog from "./BaseDialog";
import { IDialogProps } from "./IDialogProps";
import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab";
import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
import SessionManagerTab from '../settings/tabs/user/SessionManagerTab';
import SessionManagerTab from "../settings/tabs/user/SessionManagerTab";
import { UserTab } from "./UserTab";
interface IProps extends IDialogProps {
@ -67,7 +67,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
}
public componentWillUnmount(): void {
this.settingsWatchers.forEach(watcherRef => SettingsStore.unwatchSetting(watcherRef));
this.settingsWatchers.forEach((watcherRef) => SettingsStore.unwatchSetting(watcherRef));
}
private mjolnirChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
@ -83,104 +83,129 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
private getTabs() {
const tabs: Tab[] = [];
tabs.push(new Tab(
UserTab.General,
_td("General"),
"mx_UserSettingsDialog_settingsIcon",
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
"UserSettingsGeneral",
));
tabs.push(new Tab(
UserTab.Appearance,
_td("Appearance"),
"mx_UserSettingsDialog_appearanceIcon",
<AppearanceUserSettingsTab />,
"UserSettingsAppearance",
));
tabs.push(new Tab(
UserTab.Notifications,
_td("Notifications"),
"mx_UserSettingsDialog_bellIcon",
<NotificationUserSettingsTab />,
"UserSettingsNotifications",
));
tabs.push(new Tab(
UserTab.Preferences,
_td("Preferences"),
"mx_UserSettingsDialog_preferencesIcon",
<PreferencesUserSettingsTab closeSettingsFn={this.props.onFinished} />,
"UserSettingsPreferences",
));
tabs.push(new Tab(
UserTab.Keyboard,
_td("Keyboard"),
"mx_UserSettingsDialog_keyboardIcon",
<KeyboardUserSettingsTab />,
"UserSettingsKeyboard",
));
tabs.push(new Tab(
UserTab.Sidebar,
_td("Sidebar"),
"mx_UserSettingsDialog_sidebarIcon",
<SidebarUserSettingsTab />,
"UserSettingsSidebar",
));
tabs.push(
new Tab(
UserTab.General,
_td("General"),
"mx_UserSettingsDialog_settingsIcon",
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
"UserSettingsGeneral",
),
);
tabs.push(
new Tab(
UserTab.Appearance,
_td("Appearance"),
"mx_UserSettingsDialog_appearanceIcon",
<AppearanceUserSettingsTab />,
"UserSettingsAppearance",
),
);
tabs.push(
new Tab(
UserTab.Notifications,
_td("Notifications"),
"mx_UserSettingsDialog_bellIcon",
<NotificationUserSettingsTab />,
"UserSettingsNotifications",
),
);
tabs.push(
new Tab(
UserTab.Preferences,
_td("Preferences"),
"mx_UserSettingsDialog_preferencesIcon",
<PreferencesUserSettingsTab closeSettingsFn={this.props.onFinished} />,
"UserSettingsPreferences",
),
);
tabs.push(
new Tab(
UserTab.Keyboard,
_td("Keyboard"),
"mx_UserSettingsDialog_keyboardIcon",
<KeyboardUserSettingsTab />,
"UserSettingsKeyboard",
),
);
tabs.push(
new Tab(
UserTab.Sidebar,
_td("Sidebar"),
"mx_UserSettingsDialog_sidebarIcon",
<SidebarUserSettingsTab />,
"UserSettingsSidebar",
),
);
if (SettingsStore.getValue(UIFeature.Voip)) {
tabs.push(new Tab(
UserTab.Voice,
_td("Voice & Video"),
"mx_UserSettingsDialog_voiceIcon",
<VoiceUserSettingsTab />,
"UserSettingsVoiceVideo",
));
tabs.push(
new Tab(
UserTab.Voice,
_td("Voice & Video"),
"mx_UserSettingsDialog_voiceIcon",
<VoiceUserSettingsTab />,
"UserSettingsVoiceVideo",
),
);
}
tabs.push(new Tab(
UserTab.Security,
_td("Security & Privacy"),
"mx_UserSettingsDialog_securityIcon",
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
"UserSettingsSecurityPrivacy",
));
if (this.state.newSessionManagerEnabled) {
tabs.push(new Tab(
UserTab.SessionManager,
_td("Sessions"),
tabs.push(
new Tab(
UserTab.Security,
_td("Security & Privacy"),
"mx_UserSettingsDialog_securityIcon",
<SessionManagerTab />,
// don't track with posthog while under construction
undefined,
));
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
"UserSettingsSecurityPrivacy",
),
);
if (this.state.newSessionManagerEnabled) {
tabs.push(
new Tab(
UserTab.SessionManager,
_td("Sessions"),
"mx_UserSettingsDialog_securityIcon",
<SessionManagerTab />,
// don't track with posthog while under construction
undefined,
),
);
}
// Show the Labs tab if enabled or if there are any active betas
if (SdkConfig.get("show_labs_settings")
|| SettingsStore.getFeatureSettingNames().some(k => SettingsStore.getBetaInfo(k))
if (
SdkConfig.get("show_labs_settings") ||
SettingsStore.getFeatureSettingNames().some((k) => SettingsStore.getBetaInfo(k))
) {
tabs.push(new Tab(
UserTab.Labs,
_td("Labs"),
"mx_UserSettingsDialog_labsIcon",
<LabsUserSettingsTab />,
"UserSettingsLabs",
));
tabs.push(
new Tab(
UserTab.Labs,
_td("Labs"),
"mx_UserSettingsDialog_labsIcon",
<LabsUserSettingsTab />,
"UserSettingsLabs",
),
);
}
if (this.state.mjolnirEnabled) {
tabs.push(new Tab(
UserTab.Mjolnir,
_td("Ignored users"),
"mx_UserSettingsDialog_mjolnirIcon",
<MjolnirUserSettingsTab />,
"UserSettingMjolnir",
));
tabs.push(
new Tab(
UserTab.Mjolnir,
_td("Ignored users"),
"mx_UserSettingsDialog_mjolnirIcon",
<MjolnirUserSettingsTab />,
"UserSettingMjolnir",
),
);
}
tabs.push(new Tab(
UserTab.Help,
_td("Help & About"),
"mx_UserSettingsDialog_helpIcon",
<HelpUserSettingsTab closeSettingsFn={() => this.props.onFinished(true)} />,
"UserSettingsHelpAbout",
));
tabs.push(
new Tab(
UserTab.Help,
_td("Help & About"),
"mx_UserSettingsDialog_helpIcon",
<HelpUserSettingsTab closeSettingsFn={() => this.props.onFinished(true)} />,
"UserSettingsHelpAbout",
),
);
return tabs;
}
@ -188,12 +213,12 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
render() {
return (
<BaseDialog
className='mx_UserSettingsDialog'
className="mx_UserSettingsDialog"
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Settings")}
>
<div className='mx_SettingsDialog_content'>
<div className="mx_SettingsDialog_content">
<TabbedView
tabs={this.getTabs()}
initialTabId={this.props.initialTabId}

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { User } from 'matrix-js-sdk/src/models/user';
import { User } from "matrix-js-sdk/src/models/user";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
import EncryptionPanel from "../right_panel/EncryptionPanel";
@ -41,7 +41,7 @@ export default class VerificationRequestDialog extends React.Component<IProps, I
verificationRequest: this.props.verificationRequest,
};
if (this.props.verificationRequestPromise) {
this.props.verificationRequestPromise.then(r => {
this.props.verificationRequestPromise.then((r) => {
this.setState({ verificationRequest: r });
});
}
@ -50,26 +50,26 @@ export default class VerificationRequestDialog extends React.Component<IProps, I
render() {
const request = this.state.verificationRequest;
const otherUserId = request && request.otherUserId;
const member = this.props.member ||
otherUserId && MatrixClientPeg.get().getUser(otherUserId);
const title = request && request.isSelfVerification ?
_t("Verify other device") : _t("Verification Request");
const member = this.props.member || (otherUserId && MatrixClientPeg.get().getUser(otherUserId));
const title = request && request.isSelfVerification ? _t("Verify other device") : _t("Verification Request");
return <BaseDialog
className="mx_InfoDialog"
onFinished={this.props.onFinished}
contentId="mx_Dialog_content"
title={title}
hasCancel={true}
>
<EncryptionPanel
layout="dialog"
verificationRequest={this.props.verificationRequest}
verificationRequestPromise={this.props.verificationRequestPromise}
onClose={this.props.onFinished}
member={member}
isRoomEncrypted={false}
/>
</BaseDialog>;
return (
<BaseDialog
className="mx_InfoDialog"
onFinished={this.props.onFinished}
contentId="mx_Dialog_content"
title={title}
hasCancel={true}
>
<EncryptionPanel
layout="dialog"
verificationRequest={this.props.verificationRequest}
verificationRequestPromise={this.props.verificationRequestPromise}
onClose={this.props.onFinished}
member={member}
isRoomEncrypted={false}
/>
</BaseDialog>
);
}
}

View file

@ -14,14 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import {
Capability,
isTimelineCapability,
Widget,
WidgetEventCapability,
WidgetKind,
} from "matrix-widget-api";
import React from "react";
import { Capability, isTimelineCapability, Widget, WidgetEventCapability, WidgetKind } from "matrix-widget-api";
import { lexicographicCompare } from "matrix-js-sdk/src/utils";
import BaseDialog from "./BaseDialog";
@ -56,10 +50,10 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
super(props);
const parsedEvents = WidgetEventCapability.findEventCapabilities(this.props.requestedCapabilities);
parsedEvents.forEach(e => this.eventPermissionsMap.set(e.raw, e));
parsedEvents.forEach((e) => this.eventPermissionsMap.set(e.raw, e));
const states: IBooleanStates = {};
this.props.requestedCapabilities.forEach(c => states[c] = true);
this.props.requestedCapabilities.forEach((c) => (states[c] = true));
this.state = {
booleanStates: states,
@ -78,9 +72,11 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
};
private onSubmit = async (ev) => {
this.closeAndTryRemember(Object.entries(this.state.booleanStates)
.filter(([_, isSelected]) => isSelected)
.map(([cap]) => cap));
this.closeAndTryRemember(
Object.entries(this.state.booleanStates)
.filter(([_, isSelected]) => isSelected)
.map(([cap]) => cap),
);
};
private onReject = async (ev) => {
@ -107,17 +103,16 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
});
const checkboxRows = orderedCapabilities.map(([cap, isChecked], i) => {
const text = CapabilityText.for(cap, this.props.widgetKind);
const byline = text.byline
? <span className="mx_WidgetCapabilitiesPromptDialog_byline">{ text.byline }</span>
: null;
const byline = text.byline ? (
<span className="mx_WidgetCapabilitiesPromptDialog_byline">{text.byline}</span>
) : null;
return (
<div className="mx_WidgetCapabilitiesPromptDialog_cap" key={cap + i}>
<StyledCheckbox
checked={isChecked}
onChange={() => this.onToggle(cap)}
>{ text.primary }</StyledCheckbox>
{ byline }
<StyledCheckbox checked={isChecked} onChange={() => this.onToggle(cap)}>
{text.primary}
</StyledCheckbox>
{byline}
</div>
);
});
@ -130,8 +125,8 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
>
<form onSubmit={this.onSubmit}>
<div className="mx_Dialog_content">
<div className="text-muted">{ _t("This widget would like to:") }</div>
{ checkboxRows }
<div className="text-muted">{_t("This widget would like to:")}</div>
{checkboxRows}
<DialogButtons
primaryButton={_t("Approve")}
cancelButton={_t("Decline All")}
@ -142,7 +137,9 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
value={this.state.rememberSelection}
toggleInFront={true}
onChange={this.onRememberSelectionChange}
label={_t("Remember my selection for this widget")} />}
label={_t("Remember my selection for this widget")}
/>
}
/>
</div>
</form>

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { Widget, WidgetKind } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
@ -25,7 +25,7 @@ import { OIDCState } from "../../../stores/widgets/WidgetPermissionStore";
import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import { SdkContextClass } from '../../../contexts/SDKContext';
import { SdkContextClass } from "../../../contexts/SDKContext";
interface IProps extends IDialogProps {
widget: Widget;
@ -59,7 +59,9 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent<I
logger.log(`Remembering ${this.props.widget.id} as allowed=${allowed} for OpenID`);
SdkContextClass.instance.widgetPermissionStore.setOIDCState(
this.props.widget, this.props.widgetKind, this.props.inRoomId,
this.props.widget,
this.props.widgetKind,
this.props.inRoomId,
allowed ? OIDCState.Allowed : OIDCState.Denied,
);
}
@ -74,18 +76,16 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent<I
public render(): JSX.Element {
return (
<BaseDialog
className='mx_WidgetOpenIDPermissionsDialog'
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:") }
</p>
<div className="mx_WidgetOpenIDPermissionsDialog_content">
<p>{_t("The widget will verify your user ID, but won't be able to perform actions for you:")}</p>
<p className="text-muted">
{ /* cheap trim to just get the path */ }
{ this.props.widget.templateUrl.split("?")[0].split("#")[0] }
{/* cheap trim to just get the path */}
{this.props.widget.templateUrl.split("?")[0].split("#")[0]}
</p>
</div>
<DialogButtons
@ -97,7 +97,9 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent<I
value={this.state.rememberSelection}
toggleInFront={true}
onChange={this.onRememberSelectionChange}
label={_t("Remember this")} />}
label={_t("Remember this")}
/>
}
/>
</BaseDialog>
);

View file

@ -26,9 +26,7 @@ import { _t } from "../../../../languageHandler";
export const AccountDataEventEditor = ({ mxEvent, onBack }: IEditorProps) => {
const cli = useContext(MatrixClientContext);
const fields = useMemo(() => [
eventTypeField(mxEvent?.getType()),
], [mxEvent]);
const fields = useMemo(() => [eventTypeField(mxEvent?.getType())], [mxEvent]);
const onSend = ([eventType]: string[], content?: IContent) => {
return cli.setAccountData(eventType, content);
@ -42,9 +40,7 @@ export const RoomAccountDataEventEditor = ({ mxEvent, onBack }: IEditorProps) =>
const context = useContext(DevtoolsContext);
const cli = useContext(MatrixClientContext);
const fields = useMemo(() => [
eventTypeField(mxEvent?.getType()),
], [mxEvent]);
const fields = useMemo(() => [eventTypeField(mxEvent?.getType())], [mxEvent]);
const onSend = ([eventType]: string[], content?: IContent) => {
return cli.setRoomAccountData(context.room.roomId, eventType, content);
@ -75,43 +71,49 @@ const BaseAccountDataExplorer = ({ events, Editor, actionLabel, onBack, setTool
setTool(actionLabel, Editor);
};
return <BaseTool onBack={onBack} actionLabel={actionLabel} onAction={onAction}>
<FilteredList query={query} onChange={setQuery}>
{
Object.entries(events).map(([eventType, ev]) => {
return (
<BaseTool onBack={onBack} actionLabel={actionLabel} onAction={onAction}>
<FilteredList query={query} onChange={setQuery}>
{Object.entries(events).map(([eventType, ev]) => {
const onClick = () => {
setEvent(ev);
};
return <button className="mx_DevTools_button" key={eventType} onClick={onClick}>
{ eventType }
</button>;
})
}
</FilteredList>
</BaseTool>;
return (
<button className="mx_DevTools_button" key={eventType} onClick={onClick}>
{eventType}
</button>
);
})}
</FilteredList>
</BaseTool>
);
};
export const AccountDataExplorer = ({ onBack, setTool }: IDevtoolsProps) => {
const cli = useContext(MatrixClientContext);
return <BaseAccountDataExplorer
events={cli.store.accountData}
Editor={AccountDataEventEditor}
actionLabel={_t("Send custom account data event")}
onBack={onBack}
setTool={setTool}
/>;
return (
<BaseAccountDataExplorer
events={cli.store.accountData}
Editor={AccountDataEventEditor}
actionLabel={_t("Send custom account data event")}
onBack={onBack}
setTool={setTool}
/>
);
};
export const RoomAccountDataExplorer = ({ onBack, setTool }: IDevtoolsProps) => {
const context = useContext(DevtoolsContext);
return <BaseAccountDataExplorer
events={context.room.accountData}
Editor={RoomAccountDataEventEditor}
actionLabel={_t("Send custom room account data event")}
onBack={onBack}
setTool={setTool}
/>;
return (
<BaseAccountDataExplorer
events={context.room.accountData}
Editor={RoomAccountDataEventEditor}
actionLabel={_t("Send custom room account data event")}
onBack={onBack}
setTool={setTool}
/>
);
};

View file

@ -59,24 +59,18 @@ const BaseTool: React.FC<XOR<IMinProps, IProps>> = ({ className, actionLabel, on
});
};
actionButton = (
<button onClick={onActionClick}>
{ actionLabel }
</button>
);
actionButton = <button onClick={onActionClick}>{actionLabel}</button>;
}
return <>
<div className={classNames("mx_DevTools_content", className)}>
{ children }
</div>
<div className="mx_Dialog_buttons">
<button onClick={onBackClick}>
{ _t("Back") }
</button>
{ actionButton }
</div>
</>;
return (
<>
<div className={classNames("mx_DevTools_content", className)}>{children}</div>
<div className="mx_Dialog_buttons">
<button onClick={onBackClick}>{_t("Back")}</button>
{actionButton}
</div>
</>
);
};
export default BaseTool;

View file

@ -60,18 +60,20 @@ const validateEventContent = withValidation<any, Error | undefined>({
return e;
}
},
rules: [{
key: "validJson",
test: ({ value }, error) => {
if (!value) return true;
return !error;
rules: [
{
key: "validJson",
test: ({ value }, error) => {
if (!value) return true;
return !error;
},
invalid: (error) => _t("Doesn't look like valid JSON.") + " " + error,
},
invalid: (error) => _t("Doesn't look like valid JSON.") + " " + error,
}],
],
});
export const EventEditor = ({ fieldDefs, defaultContent = "{\n\n}", onSend, onBack }: IEventEditorProps) => {
const [fieldData, setFieldData] = useState<string[]>(fieldDefs.map(def => def.default ?? ""));
const [fieldData, setFieldData] = useState<string[]>(fieldDefs.map((def) => def.default ?? ""));
const [content, setContent] = useState<string>(defaultContent);
const contentField = useRef<Field>();
@ -85,10 +87,12 @@ export const EventEditor = ({ fieldDefs, defaultContent = "{\n\n}", onSend, onBa
type="text"
autoComplete="on"
value={fieldData[i]}
onChange={ev => setFieldData(data => {
data[i] = ev.target.value;
return [...data];
})}
onChange={(ev) =>
setFieldData((data) => {
data[i] = ev.target.value;
return [...data];
})
}
/>
));
@ -110,29 +114,25 @@ export const EventEditor = ({ fieldDefs, defaultContent = "{\n\n}", onSend, onBa
return _t("Event sent!");
};
return <BaseTool
actionLabel={_t("Send")}
onAction={onAction}
onBack={onBack}
>
<div className="mx_DevTools_eventTypeStateKeyGroup">
{ fields }
</div>
return (
<BaseTool actionLabel={_t("Send")} onAction={onAction} onBack={onBack}>
<div className="mx_DevTools_eventTypeStateKeyGroup">{fields}</div>
<Field
id="evContent"
label={_t("Event Content")}
type="text"
className="mx_DevTools_textarea"
autoComplete="off"
value={content}
onChange={ev => setContent(ev.target.value)}
element="textarea"
onValidate={validateEventContent}
ref={contentField}
autoFocus={!!defaultContent}
/>
</BaseTool>;
<Field
id="evContent"
label={_t("Event Content")}
type="text"
className="mx_DevTools_textarea"
autoComplete="off"
value={content}
onChange={(ev) => setContent(ev.target.value)}
element="textarea"
onValidate={validateEventContent}
ref={contentField}
autoFocus={!!defaultContent}
/>
</BaseTool>
);
};
export interface IEditorProps extends Pick<IDevtoolsProps, "onBack"> {
@ -157,11 +157,11 @@ export const EventViewer = ({ mxEvent, onBack, Editor }: IViewerProps) => {
setEditing(true);
};
return <BaseTool onBack={onBack} actionLabel={_t("Edit")} onAction={onAction}>
<SyntaxHighlight language="json">
{ stringify(mxEvent.event) }
</SyntaxHighlight>
</BaseTool>;
return (
<BaseTool onBack={onBack} actionLabel={_t("Edit")} onAction={onAction}>
<SyntaxHighlight language="json">{stringify(mxEvent.event)}</SyntaxHighlight>
</BaseTool>
);
};
// returns the id of the initial message, not the id of the previous edit
@ -175,9 +175,7 @@ export const TimelineEventEditor = ({ mxEvent, onBack }: IEditorProps) => {
const context = useContext(DevtoolsContext);
const cli = useContext(MatrixClientContext);
const fields = useMemo(() => [
eventTypeField(mxEvent?.getType()),
], [mxEvent]);
const fields = useMemo(() => [eventTypeField(mxEvent?.getType())], [mxEvent]);
const onSend = ([eventType]: string[], content?: IContent) => {
return cli.sendEvent(context.room.roomId, eventType, content);

View file

@ -53,38 +53,43 @@ const FilteredList = ({ children, query, onChange }: IProps) => {
const createOverflowElement = (overflowCount: number, totalCount: number) => {
const showMore = () => {
setTruncateAt(num => num + LOAD_TILES_STEP_SIZE);
setTruncateAt((num) => num + LOAD_TILES_STEP_SIZE);
};
return <button className="mx_DevTools_button" onClick={showMore}>
{ _t("and %(count)s others...", { count: overflowCount }) }
</button>;
return (
<button className="mx_DevTools_button" onClick={showMore}>
{_t("and %(count)s others...", { count: overflowCount })}
</button>
);
};
return <>
<Field
label={_t('Filter results')}
autoFocus={true}
size={64}
type="text"
autoComplete="off"
value={query}
onChange={ev => onChange(ev.target.value)}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
// force re-render so that autoFocus is applied when this component is re-used
key={children?.[0]?.key ?? ''}
/>
{ filteredChildren.length < 1
? _t("No results found")
: <TruncatedList
getChildren={getChildren}
getChildCount={getChildCount}
truncateAt={truncateAt}
createOverflowElement={createOverflowElement}
return (
<>
<Field
label={_t("Filter results")}
autoFocus={true}
size={64}
type="text"
autoComplete="off"
value={query}
onChange={(ev) => onChange(ev.target.value)}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
// force re-render so that autoFocus is applied when this component is re-used
key={children?.[0]?.key ?? ""}
/>
}
</>;
{filteredChildren.length < 1 ? (
_t("No results found")
) : (
<TruncatedList
getChildren={getChildren}
getChildCount={getChildCount}
truncateAt={truncateAt}
createOverflowElement={createOverflowElement}
/>
)}
</>
);
};
export default FilteredList;

View file

@ -28,10 +28,10 @@ export const StateEventEditor = ({ mxEvent, onBack }: IEditorProps) => {
const context = useContext(DevtoolsContext);
const cli = useContext(MatrixClientContext);
const fields = useMemo(() => [
eventTypeField(mxEvent?.getType()),
stateKeyField(mxEvent?.getStateKey()),
], [mxEvent]);
const fields = useMemo(
() => [eventTypeField(mxEvent?.getType()), stateKeyField(mxEvent?.getStateKey())],
[mxEvent],
);
const onSend = ([eventType, stateKey]: string[], content?: IContent) => {
return cli.sendStateEvent(context.room.roomId, eventType, content, stateKey);
@ -49,15 +49,17 @@ interface StateEventButtonProps {
const StateEventButton = ({ label, onClick }: StateEventButtonProps) => {
const trimmed = label.trim();
return <button
className={classNames("mx_DevTools_button", {
mx_DevTools_RoomStateExplorer_button_hasSpaces: trimmed.length !== label.length,
mx_DevTools_RoomStateExplorer_button_emptyString: !trimmed,
})}
onClick={onClick}
>
{ trimmed ? label : _t("<%(count)s spaces>", { count: label.length }) }
</button>;
return (
<button
className={classNames("mx_DevTools_button", {
mx_DevTools_RoomStateExplorer_button_hasSpaces: trimmed.length !== label.length,
mx_DevTools_RoomStateExplorer_button_emptyString: !trimmed,
})}
onClick={onClick}
>
{trimmed ? label : _t("<%(count)s spaces>", { count: label.length })}
</button>
);
};
interface IEventTypeProps extends Pick<IDevtoolsProps, "onBack"> {
@ -90,15 +92,15 @@ const RoomStateExplorerEventType = ({ eventType, onBack }: IEventTypeProps) => {
return <EventViewer mxEvent={event} onBack={_onBack} Editor={StateEventEditor} />;
}
return <BaseTool onBack={onBack}>
<FilteredList query={query} onChange={setQuery}>
{
Array.from(events.entries()).map(([stateKey, ev]) => (
return (
<BaseTool onBack={onBack}>
<FilteredList query={query} onChange={setQuery}>
{Array.from(events.entries()).map(([stateKey, ev]) => (
<StateEventButton key={stateKey} label={stateKey} onClick={() => setEvent(ev)} />
))
}
</FilteredList>
</BaseTool>;
))}
</FilteredList>
</BaseTool>
);
};
export const RoomStateExplorer = ({ onBack, setTool }: IDevtoolsProps) => {
@ -119,13 +121,13 @@ export const RoomStateExplorer = ({ onBack, setTool }: IDevtoolsProps) => {
setTool(_t("Send custom state event"), StateEventEditor);
};
return <BaseTool onBack={onBack} actionLabel={_t("Send custom state event")} onAction={onAction}>
<FilteredList query={query} onChange={setQuery}>
{
Array.from(events.keys()).map((eventType) => (
return (
<BaseTool onBack={onBack} actionLabel={_t("Send custom state event")} onAction={onAction}>
<FilteredList query={query} onChange={setQuery}>
{Array.from(events.keys()).map((eventType) => (
<StateEventButton key={eventType} label={eventType} onClick={() => setEventType(eventType)} />
))
}
</FilteredList>
</BaseTool>;
))}
</FilteredList>
</BaseTool>
);
};

View file

@ -66,30 +66,33 @@ const ServerInfo = ({ onBack }: IDevtoolsProps) => {
if (!capabilities || !clientVersions || !serverVersions) {
body = <Spinner />;
} else {
body = <>
<h4>{ _t("Capabilities") }</h4>
{ capabilities !== FAILED_TO_LOAD
? <SyntaxHighlight language="json" children={JSON.stringify(capabilities, null, 4)} />
: <div>{ _t("Failed to load.") }</div>
}
body = (
<>
<h4>{_t("Capabilities")}</h4>
{capabilities !== FAILED_TO_LOAD ? (
<SyntaxHighlight language="json" children={JSON.stringify(capabilities, null, 4)} />
) : (
<div>{_t("Failed to load.")}</div>
)}
<h4>{ _t("Client Versions") }</h4>
{ clientVersions !== FAILED_TO_LOAD
? <SyntaxHighlight language="json" children={JSON.stringify(clientVersions, null, 4)} />
: <div>{ _t("Failed to load.") }</div>
}
<h4>{_t("Client Versions")}</h4>
{clientVersions !== FAILED_TO_LOAD ? (
<SyntaxHighlight language="json" children={JSON.stringify(clientVersions, null, 4)} />
) : (
<div>{_t("Failed to load.")}</div>
)}
<h4>{ _t("Server Versions") }</h4>
{ serverVersions !== FAILED_TO_LOAD
? <SyntaxHighlight language="json" children={JSON.stringify(serverVersions, null, 4)} />
: <div>{ _t("Failed to load.") }</div>
}
</>;
<h4>{_t("Server Versions")}</h4>
{serverVersions !== FAILED_TO_LOAD ? (
<SyntaxHighlight language="json" children={JSON.stringify(serverVersions, null, 4)} />
) : (
<div>{_t("Failed to load.")}</div>
)}
</>
);
}
return <BaseTool onBack={onBack}>
{ body }
</BaseTool>;
return <BaseTool onBack={onBack}>{body}</BaseTool>;
};
export default ServerInfo;

View file

@ -25,7 +25,7 @@ const ServersInRoom = ({ onBack }: IDevtoolsProps) => {
const servers = useMemo<Record<string, number>>(() => {
const servers: Record<string, number> = {};
context.room.currentState.getStateEvents(EventType.RoomMember).forEach(ev => {
context.room.currentState.getStateEvents(EventType.RoomMember).forEach((ev) => {
if (ev.getContent().membership !== "join") return; // only count joined users
const server = ev.getSender().split(":")[1];
servers[server] = (servers[server] ?? 0) + 1;
@ -33,24 +33,26 @@ const ServersInRoom = ({ onBack }: IDevtoolsProps) => {
return servers;
}, [context.room]);
return <BaseTool onBack={onBack}>
<table>
<thead>
<tr>
<th>{ _t("Server") }</th>
<th>{ _t("Number of users") }</th>
</tr>
</thead>
<tbody>
{ Object.entries(servers).map(([server, numUsers]) => (
<tr key={server}>
<td>{ server }</td>
<td>{ numUsers }</td>
return (
<BaseTool onBack={onBack}>
<table>
<thead>
<tr>
<th>{_t("Server")}</th>
<th>{_t("Number of users")}</th>
</tr>
)) }
</tbody>
</table>
</BaseTool>;
</thead>
<tbody>
{Object.entries(servers).map(([server, numUsers]) => (
<tr key={server}>
<td>{server}</td>
<td>{numUsers}</td>
</tr>
))}
</tbody>
</table>
</BaseTool>
);
};
export default ServersInRoom;

View file

@ -66,7 +66,11 @@ interface ICanEditLevelFieldProps {
const CanEditLevelField = ({ setting, roomId, level }: ICanEditLevelFieldProps) => {
const canEdit = SettingsStore.canSetValue(setting, roomId, level);
const className = canEdit ? "mx_DevTools_SettingsExplorer_mutable" : "mx_DevTools_SettingsExplorer_immutable";
return <td className={className}><code>{ canEdit.toString() }</code></td>;
return (
<td className={className}>
<code>{canEdit.toString()}</code>
</td>
);
};
function renderExplicitSettingValues(setting: string, roomId: string): string {
@ -91,8 +95,9 @@ interface IEditSettingProps extends Pick<IDevtoolsProps, "onBack"> {
const EditSetting = ({ setting, onBack }: IEditSettingProps) => {
const context = useContext(DevtoolsContext);
const [explicitValue, setExplicitValue] = useState(renderExplicitSettingValues(setting, null));
const [explicitRoomValue, setExplicitRoomValue] =
useState(renderExplicitSettingValues(setting, context.room.roomId));
const [explicitRoomValue, setExplicitRoomValue] = useState(
renderExplicitSettingValues(setting, context.room.roomId),
);
const onSave = async () => {
try {
@ -124,65 +129,73 @@ const EditSetting = ({ setting, onBack }: IEditSettingProps) => {
}
};
return <BaseTool onBack={onBack} actionLabel={_t("Save setting values")} onAction={onSave}>
<h3>{ _t("Setting:") } <code>{ setting }</code></h3>
return (
<BaseTool onBack={onBack} actionLabel={_t("Save setting values")} onAction={onSave}>
<h3>
{_t("Setting:")} <code>{setting}</code>
</h3>
<div className="mx_DevTools_SettingsExplorer_warning">
<b>{ _t("Caution:") }</b> { _t("This UI does NOT check the types of the values. Use at your own risk.") }
</div>
<div className="mx_DevTools_SettingsExplorer_warning">
<b>{_t("Caution:")}</b> {_t("This UI does NOT check the types of the values. Use at your own risk.")}
</div>
<div>
{ _t("Setting definition:") }
<pre><code>{ JSON.stringify(SETTINGS[setting], null, 4) }</code></pre>
</div>
<div>
{_t("Setting definition:")}
<pre>
<code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
</pre>
</div>
<div>
<table>
<thead>
<tr>
<th>{ _t("Level") }</th>
<th>{ _t("Settable at global") }</th>
<th>{ _t("Settable at room") }</th>
</tr>
</thead>
<tbody>
{ LEVEL_ORDER.map(lvl => (
<tr key={lvl}>
<td><code>{ lvl }</code></td>
<CanEditLevelField setting={setting} level={lvl} />
<CanEditLevelField setting={setting} roomId={context.room.roomId} level={lvl} />
<div>
<table>
<thead>
<tr>
<th>{_t("Level")}</th>
<th>{_t("Settable at global")}</th>
<th>{_t("Settable at room")}</th>
</tr>
)) }
</tbody>
</table>
</div>
</thead>
<tbody>
{LEVEL_ORDER.map((lvl) => (
<tr key={lvl}>
<td>
<code>{lvl}</code>
</td>
<CanEditLevelField setting={setting} level={lvl} />
<CanEditLevelField setting={setting} roomId={context.room.roomId} level={lvl} />
</tr>
))}
</tbody>
</table>
</div>
<div>
<Field
id="valExpl"
label={_t("Values at explicit levels")}
type="text"
className="mx_DevTools_textarea"
element="textarea"
autoComplete="off"
value={explicitValue}
onChange={e => setExplicitValue(e.target.value)}
/>
</div>
<div>
<Field
id="valExpl"
label={_t("Values at explicit levels")}
type="text"
className="mx_DevTools_textarea"
element="textarea"
autoComplete="off"
value={explicitValue}
onChange={(e) => setExplicitValue(e.target.value)}
/>
</div>
<div>
<Field
id="valExpl"
label={_t("Values at explicit levels in this room")}
type="text"
className="mx_DevTools_textarea"
element="textarea"
autoComplete="off"
value={explicitRoomValue}
onChange={e => setExplicitRoomValue(e.target.value)}
/>
</div>
</BaseTool>;
<div>
<Field
id="valExpl"
label={_t("Values at explicit levels in this room")}
type="text"
className="mx_DevTools_textarea"
element="textarea"
autoComplete="off"
value={explicitRoomValue}
onChange={(e) => setExplicitRoomValue(e.target.value)}
/>
</div>
</BaseTool>
);
};
interface IViewSettingProps extends Pick<IDevtoolsProps, "onBack"> {
@ -193,40 +206,50 @@ interface IViewSettingProps extends Pick<IDevtoolsProps, "onBack"> {
const ViewSetting = ({ setting, onEdit, onBack }: IViewSettingProps) => {
const context = useContext(DevtoolsContext);
return <BaseTool onBack={onBack} actionLabel={_t("Edit values")} onAction={onEdit}>
<h3>{ _t("Setting:") } <code>{ setting }</code></h3>
return (
<BaseTool onBack={onBack} actionLabel={_t("Edit values")} onAction={onEdit}>
<h3>
{_t("Setting:")} <code>{setting}</code>
</h3>
<div>
{ _t("Setting definition:") }
<pre><code>{ JSON.stringify(SETTINGS[setting], null, 4) }</code></pre>
</div>
<div>
{_t("Setting definition:")}
<pre>
<code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
</pre>
</div>
<div>
{ _t("Value:") }&nbsp;
<code>{ renderSettingValue(SettingsStore.getValue(setting)) }</code>
</div>
<div>
{_t("Value:")}&nbsp;
<code>{renderSettingValue(SettingsStore.getValue(setting))}</code>
</div>
<div>
{ _t("Value in this room:") }&nbsp;
<code>{ renderSettingValue(SettingsStore.getValue(setting, context.room.roomId)) }</code>
</div>
<div>
{_t("Value in this room:")}&nbsp;
<code>{renderSettingValue(SettingsStore.getValue(setting, context.room.roomId))}</code>
</div>
<div>
{ _t("Values at explicit levels:") }
<pre><code>{ renderExplicitSettingValues(setting, null) }</code></pre>
</div>
<div>
{_t("Values at explicit levels:")}
<pre>
<code>{renderExplicitSettingValues(setting, null)}</code>
</pre>
</div>
<div>
{ _t("Values at explicit levels in this room:") }
<pre><code>{ renderExplicitSettingValues(setting, context.room.roomId) }</code></pre>
</div>
</BaseTool>;
<div>
{_t("Values at explicit levels in this room:")}
<pre>
<code>{renderExplicitSettingValues(setting, context.room.roomId)}</code>
</pre>
</div>
</BaseTool>
);
};
function renderSettingValue(val: any): string {
// Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us
const toStringTypes = ["boolean", "number"];
if (toStringTypes.includes(typeof(val))) {
if (toStringTypes.includes(typeof val)) {
return val.toString();
} else {
return JSON.stringify(val);
@ -246,60 +269,60 @@ const SettingsList = ({ onBack, onView, onEdit }: ISettingsListProps) => {
let allSettings = Object.keys(SETTINGS);
if (query) {
const lcQuery = query.toLowerCase();
allSettings = allSettings.filter(setting => setting.toLowerCase().includes(lcQuery));
allSettings = allSettings.filter((setting) => setting.toLowerCase().includes(lcQuery));
}
return allSettings;
}, [query]);
return <BaseTool onBack={onBack} className="mx_DevTools_SettingsExplorer">
<Field
label={_t("Filter results")}
autoFocus={true}
size={64}
type="text"
autoComplete="off"
value={query}
onChange={ev => setQuery(ev.target.value)}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
/>
<table>
<thead>
<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>
<AccessibleButton
kind="link_inline"
className="mx_DevTools_SettingsExplorer_setting"
onClick={() => onView(i)}
>
<code>{ i }</code>
</AccessibleButton>
<AccessibleButton
alt={_t("Edit setting")}
onClick={() => onEdit(i)}
className="mx_DevTools_SettingsExplorer_edit"
>
</AccessibleButton>
</td>
<td>
<code>{ renderSettingValue(SettingsStore.getValue(i)) }</code>
</td>
<td>
<code>
{ renderSettingValue(SettingsStore.getValue(i, context.room.roomId)) }
</code>
</td>
return (
<BaseTool onBack={onBack} className="mx_DevTools_SettingsExplorer">
<Field
label={_t("Filter results")}
autoFocus={true}
size={64}
type="text"
autoComplete="off"
value={query}
onChange={(ev) => setQuery(ev.target.value)}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
/>
<table>
<thead>
<tr>
<th>{_t("Setting ID")}</th>
<th>{_t("Value")}</th>
<th>{_t("Value in this room")}</th>
</tr>
)) }
</tbody>
</table>
</BaseTool>;
</thead>
<tbody>
{allSettings.map((i) => (
<tr key={i}>
<td>
<AccessibleButton
kind="link_inline"
className="mx_DevTools_SettingsExplorer_setting"
onClick={() => onView(i)}
>
<code>{i}</code>
</AccessibleButton>
<AccessibleButton
alt={_t("Edit setting")}
onClick={() => onEdit(i)}
className="mx_DevTools_SettingsExplorer_edit"
>
</AccessibleButton>
</td>
<td>
<code>{renderSettingValue(SettingsStore.getValue(i))}</code>
</td>
<td>
<code>{renderSettingValue(SettingsStore.getValue(i, context.room.roomId))}</code>
</td>
</tr>
))}
</tbody>
</table>
</BaseTool>
);
};

View file

@ -55,25 +55,29 @@ const VerificationRequestExplorer: React.FC<{
setRequestTimeout(request.timeout);
}, 500);
return () => { clearInterval(id); };
return () => {
clearInterval(id);
};
}, [request]);
return (<div className="mx_DevTools_VerificationRequest">
<dl>
<dt>{ _t("Transaction") }</dt>
<dd>{ txnId }</dd>
<dt>{ _t("Phase") }</dt>
<dd>{ PHASE_MAP[request.phase] ? _t(PHASE_MAP[request.phase]) : request.phase }</dd>
<dt>{ _t("Timeout") }</dt>
<dd>{ Math.floor(timeout / 1000) }</dd>
<dt>{ _t("Methods") }</dt>
<dd>{ request.methods && request.methods.join(", ") }</dd>
<dt>{ _t("Requester") }</dt>
<dd>{ request.requestingUserId }</dd>
<dt>{ _t("Observe only") }</dt>
<dd>{ JSON.stringify(request.observeOnly) }</dd>
</dl>
</div>);
return (
<div className="mx_DevTools_VerificationRequest">
<dl>
<dt>{_t("Transaction")}</dt>
<dd>{txnId}</dd>
<dt>{_t("Phase")}</dt>
<dd>{PHASE_MAP[request.phase] ? _t(PHASE_MAP[request.phase]) : request.phase}</dd>
<dt>{_t("Timeout")}</dt>
<dd>{Math.floor(timeout / 1000)}</dd>
<dt>{_t("Methods")}</dt>
<dd>{request.methods && request.methods.join(", ")}</dd>
<dt>{_t("Requester")}</dt>
<dd>{request.requestingUserId}</dd>
<dt>{_t("Observe only")}</dt>
<dd>{JSON.stringify(request.observeOnly)}</dd>
</dl>
</div>
);
};
const VerificationExplorer = ({ onBack }: IDevtoolsProps) => {
@ -81,16 +85,22 @@ const VerificationExplorer = ({ onBack }: IDevtoolsProps) => {
const context = useContext(DevtoolsContext);
const requests = useTypedEventEmitterState(cli, CryptoEvent.VerificationRequest, () => {
return cli.crypto.inRoomVerificationRequests["requestsByRoomId"]?.get(context.room.roomId)
?? new Map<string, VerificationRequest>();
return (
cli.crypto.inRoomVerificationRequests["requestsByRoomId"]?.get(context.room.roomId) ??
new Map<string, VerificationRequest>()
);
});
return <BaseTool onBack={onBack}>
{ Array.from(requests.entries()).reverse().map(([txnId, request]) =>
<VerificationRequestExplorer txnId={txnId} request={request} key={txnId} />,
) }
{ requests.size < 1 && _t("No verification requests found") }
</BaseTool>;
return (
<BaseTool onBack={onBack}>
{Array.from(requests.entries())
.reverse()
.map(([txnId, request]) => (
<VerificationRequestExplorer txnId={txnId} request={request} key={txnId} />
))}
{requests.size < 1 && _t("No verification requests found")}
</BaseTool>
);
};
export default VerificationExplorer;

View file

@ -43,26 +43,30 @@ const WidgetExplorer = ({ onBack }: IDevtoolsProps) => {
Array.from(context.room.currentState.events.values()).map((e: Map<string, MatrixEvent>) => {
return e.values();
}),
).reduce((p, c) => { p.push(...c); return p; }, []);
const event = allState.find(ev => ev.getId() === widget.eventId);
if (!event) { // "should never happen"
return <BaseTool onBack={onBack}>
{ _t("There was an error finding this widget.") }
</BaseTool>;
).reduce((p, c) => {
p.push(...c);
return p;
}, []);
const event = allState.find((ev) => ev.getId() === widget.eventId);
if (!event) {
// "should never happen"
return <BaseTool onBack={onBack}>{_t("There was an error finding this widget.")}</BaseTool>;
}
return <StateEventEditor mxEvent={event} onBack={onBack} />;
}
return <BaseTool onBack={onBack}>
<FilteredList query={query} onChange={setQuery}>
{ widgets.map(w => (
<button className="mx_DevTools_button" key={w.url + w.eventId} onClick={() => setWidget(w)}>
{ w.url }
</button>
)) }
</FilteredList>
</BaseTool>;
return (
<BaseTool onBack={onBack}>
<FilteredList query={query} onChange={setQuery}>
{widgets.map((w) => (
<button className="mx_DevTools_button" key={w.url + w.eventId} onClick={() => setWidget(w)}>
{w.url}
</button>
))}
</FilteredList>
</BaseTool>
);
};
export default WidgetExplorer;

View file

@ -15,15 +15,15 @@ limitations under the License.
*/
import { debounce } from "lodash";
import classNames from 'classnames';
import React, { ChangeEvent, FormEvent } from 'react';
import classNames from "classnames";
import React, { ChangeEvent, FormEvent } from "react";
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
import Field from '../../elements/Field';
import AccessibleButton from '../../elements/AccessibleButton';
import { _t } from '../../../../languageHandler';
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import Field from "../../elements/Field";
import AccessibleButton from "../../elements/AccessibleButton";
import { _t } from "../../../../languageHandler";
import { IDialogProps } from "../IDialogProps";
import { accessSecretStorage } from "../../../../SecurityManager";
import Modal from "../../../../Modal";
@ -42,7 +42,7 @@ const VALIDATION_THROTTLE_MS = 200;
interface IProps extends IDialogProps {
keyInfo: ISecretStorageKeyInfo;
checkPrivateKey: (k: {passphrase?: string, recoveryKey?: string}) => boolean;
checkPrivateKey: (k: { passphrase?: string; recoveryKey?: string }) => boolean;
}
interface IState {
@ -71,7 +71,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
recoveryKeyCorrect: null,
recoveryKeyFileError: null,
forceRecoveryKey: false,
passPhrase: '',
passPhrase: "",
keyMatches: null,
resetting: false,
};
@ -95,7 +95,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
}, VALIDATION_THROTTLE_MS);
private async validateRecoveryKey() {
if (this.state.recoveryKey === '') {
if (this.state.recoveryKey === "") {
this.setState({
recoveryKeyValid: null,
recoveryKeyCorrect: null,
@ -106,9 +106,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
try {
const cli = MatrixClientPeg.get();
const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
const correct = await cli.checkSecretStorageKey(
decodedKey, this.props.keyInfo,
);
const correct = await cli.checkSecretStorageKey(decodedKey, this.props.keyInfo);
this.setState({
recoveryKeyValid: true,
recoveryKeyCorrect: correct,
@ -165,7 +163,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
recoveryKeyFileError: true,
recoveryKeyCorrect: false,
recoveryKeyValid: false,
recoveryKey: '',
recoveryKey: "",
});
}
}
@ -264,28 +262,32 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
} else if (this.state.recoveryKeyValid) {
return _t("Wrong Security Key");
} else if (this.state.recoveryKeyValid === null) {
return '';
return "";
} else {
return _t("Invalid Security Key");
}
}
render() {
const hasPassphrase = (
const hasPassphrase =
this.props.keyInfo &&
this.props.keyInfo.passphrase &&
this.props.keyInfo.passphrase.salt &&
this.props.keyInfo.passphrase.iterations
);
this.props.keyInfo.passphrase.iterations;
const resetButton = (
<div className="mx_AccessSecretStorageDialog_reset">
{ _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
a: (sub) => <AccessibleButton
kind="link_inline"
onClick={this.onResetAllClick}
className="mx_AccessSecretStorageDialog_reset_link">{ sub }</AccessibleButton>,
}) }
{_t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
a: (sub) => (
<AccessibleButton
kind="link_inline"
onClick={this.onResetAllClick}
className="mx_AccessSecretStorageDialog_reset_link"
>
{sub}
</AccessibleButton>
),
})}
</div>
);
@ -294,149 +296,162 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
let titleClass;
if (this.state.resetting) {
title = _t("Reset everything");
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_resetBadge'];
content = <div>
<p>{ _t("Only do this if you have no other device to complete verification with.") }</p>
<p>{ _t("If you reset everything, you will restart with no trusted sessions, no trusted users, and "
+ "might not be able to see past messages.") }</p>
<DialogButtons
primaryButton={_t('Reset')}
onPrimaryButtonClick={this.onConfirmResetAllClick}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
primaryButtonClass="danger"
/>
</div>;
titleClass = ["mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_resetBadge"];
content = (
<div>
<p>{_t("Only do this if you have no other device to complete verification with.")}</p>
<p>
{_t(
"If you reset everything, you will restart with no trusted sessions, no trusted users, and " +
"might not be able to see past messages.",
)}
</p>
<DialogButtons
primaryButton={_t("Reset")}
onPrimaryButtonClick={this.onConfirmResetAllClick}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
primaryButtonClass="danger"
/>
</div>
);
} else if (hasPassphrase && !this.state.forceRecoveryKey) {
title = _t("Security Phrase");
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle'];
titleClass = ["mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle"];
let keyStatus;
if (this.state.keyMatches === false) {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{ "\uD83D\uDC4E " }{ _t(
"Unable to access secret storage. " +
"Please verify that you entered the correct Security Phrase.",
) }
</div>;
keyStatus = (
<div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4E "}
{_t(
"Unable to access secret storage. " +
"Please verify that you entered the correct Security Phrase.",
)}
</div>
);
} else {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus" />;
}
content = <div>
<p>{ _t(
"Enter your Security Phrase or <button>use your Security Key</button> to continue.", {},
{
button: s => <AccessibleButton
kind="link_inline"
onClick={this.onUseRecoveryKeyClick}
>
{ s }
</AccessibleButton>,
},
) }</p>
content = (
<div>
<p>
{_t(
"Enter your Security Phrase or <button>use your Security Key</button> to continue.",
{},
{
button: (s) => (
<AccessibleButton kind="link_inline" onClick={this.onUseRecoveryKeyClick}>
{s}
</AccessibleButton>
),
},
)}
</p>
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this.onPassPhraseNext}>
<Field
id="mx_passPhraseInput"
className="mx_AccessSecretStorageDialog_passPhraseInput"
type="password"
label={_t("Security Phrase")}
value={this.state.passPhrase}
onChange={this.onPassPhraseChange}
autoFocus={true}
autoComplete="new-password"
/>
{ keyStatus }
<DialogButtons
primaryButton={_t('Continue')}
onPrimaryButtonClick={this.onPassPhraseNext}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
primaryDisabled={this.state.passPhrase.length === 0}
additive={resetButton}
/>
</form>
</div>;
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this.onPassPhraseNext}>
<Field
id="mx_passPhraseInput"
className="mx_AccessSecretStorageDialog_passPhraseInput"
type="password"
label={_t("Security Phrase")}
value={this.state.passPhrase}
onChange={this.onPassPhraseChange}
autoFocus={true}
autoComplete="new-password"
/>
{keyStatus}
<DialogButtons
primaryButton={_t("Continue")}
onPrimaryButtonClick={this.onPassPhraseNext}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
primaryDisabled={this.state.passPhrase.length === 0}
additive={resetButton}
/>
</form>
</div>
);
} else {
title = _t("Security Key");
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle'];
titleClass = ["mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle"];
const feedbackClasses = classNames({
'mx_AccessSecretStorageDialog_recoveryKeyFeedback': true,
'mx_AccessSecretStorageDialog_recoveryKeyFeedback--valid': this.state.recoveryKeyCorrect === true,
'mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid': this.state.recoveryKeyCorrect === false,
"mx_AccessSecretStorageDialog_recoveryKeyFeedback": true,
"mx_AccessSecretStorageDialog_recoveryKeyFeedback--valid": this.state.recoveryKeyCorrect === true,
"mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid": this.state.recoveryKeyCorrect === false,
});
const recoveryKeyFeedback = <div className={feedbackClasses}>
{ this.getKeyValidationText() }
</div>;
const recoveryKeyFeedback = <div className={feedbackClasses}>{this.getKeyValidationText()}</div>;
content = <div>
<p>{ _t("Use your Security Key to continue.") }</p>
content = (
<div>
<p>{_t("Use your Security Key to continue.")}</p>
<form
className="mx_AccessSecretStorageDialog_primaryContainer"
onSubmit={this.onRecoveryKeyNext}
spellCheck={false}
autoComplete="off"
>
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry">
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput">
<Field
type="password"
id="mx_securityKey"
label={_t('Security Key')}
value={this.state.recoveryKey}
onChange={this.onRecoveryKeyChange}
forceValidity={this.state.recoveryKeyCorrect}
autoComplete="off"
/>
<form
className="mx_AccessSecretStorageDialog_primaryContainer"
onSubmit={this.onRecoveryKeyNext}
spellCheck={false}
autoComplete="off"
>
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry">
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput">
<Field
type="password"
id="mx_securityKey"
label={_t("Security Key")}
value={this.state.recoveryKey}
onChange={this.onRecoveryKeyChange}
forceValidity={this.state.recoveryKeyCorrect}
autoComplete="off"
/>
</div>
<span className="mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText">
{_t("%(securityKey)s or %(recoveryFile)s", {
recoveryFile: "",
securityKey: "",
})}
</span>
<div>
<input
type="file"
className="mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput"
ref={this.fileUpload}
onClick={chromeFileInputFix}
onChange={this.onRecoveryKeyFileChange}
/>
<AccessibleButton kind="primary" onClick={this.onRecoveryKeyFileUploadClick}>
{_t("Upload")}
</AccessibleButton>
</div>
</div>
<span className="mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText">
{ _t("%(securityKey)s or %(recoveryFile)s", {
recoveryFile: "",
securityKey: "",
}) }
</span>
<div>
<input type="file"
className="mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput"
ref={this.fileUpload}
onClick={chromeFileInputFix}
onChange={this.onRecoveryKeyFileChange}
/>
<AccessibleButton kind="primary" onClick={this.onRecoveryKeyFileUploadClick}>
{ _t("Upload") }
</AccessibleButton>
</div>
</div>
{ recoveryKeyFeedback }
<DialogButtons
primaryButton={_t('Continue')}
onPrimaryButtonClick={this.onRecoveryKeyNext}
hasCancel={true}
cancelButton={_t("Go Back")}
cancelButtonClass='danger'
onCancel={this.onCancel}
focus={false}
primaryDisabled={!this.state.recoveryKeyValid}
additive={resetButton}
/>
</form>
</div>;
{recoveryKeyFeedback}
<DialogButtons
primaryButton={_t("Continue")}
onPrimaryButtonClick={this.onRecoveryKeyNext}
hasCancel={true}
cancelButton={_t("Go Back")}
cancelButtonClass="danger"
onCancel={this.onCancel}
focus={false}
primaryDisabled={!this.state.recoveryKeyValid}
additive={resetButton}
/>
</form>
</div>
);
}
return (
<BaseDialog className='mx_AccessSecretStorageDialog'
<BaseDialog
className="mx_AccessSecretStorageDialog"
onFinished={this.props.onFinished}
title={title}
titleClass={titleClass}
>
<div>
{ content }
</div>
<div>{content}</div>
</BaseDialog>
);
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from "../../../../languageHandler";
import BaseDialog from "../BaseDialog";
@ -36,19 +36,19 @@ export default class ConfirmDestroyCrossSigningDialog extends React.Component<IP
render() {
return (
<BaseDialog
className='mx_ConfirmDestroyCrossSigningDialog'
className="mx_ConfirmDestroyCrossSigningDialog"
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Destroy cross-signing keys?")}
>
<div className='mx_ConfirmDestroyCrossSigningDialog_content'>
<div className="mx_ConfirmDestroyCrossSigningDialog_content">
<p>
{ _t(
{_t(
"Deleting cross-signing keys is permanent. " +
"Anyone you have verified with will see security alerts. " +
"You almost certainly don't want to do this, unless " +
"you've lost every device you can cross-sign from.",
) }
"Anyone you have verified with will see security alerts. " +
"You almost certainly don't want to do this, unless " +
"you've lost every device you can cross-sign from.",
)}
</p>
</div>
<DialogButtons

View file

@ -15,18 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { CrossSigningKeys } from 'matrix-js-sdk/src/client';
import React from "react";
import { CrossSigningKeys } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
import { _t } from '../../../../languageHandler';
import Modal from '../../../../Modal';
import { SSOAuthEntry } from '../../auth/InteractiveAuthEntryComponents';
import DialogButtons from '../../elements/DialogButtons';
import BaseDialog from '../BaseDialog';
import Spinner from '../../elements/Spinner';
import InteractiveAuthDialog from '../InteractiveAuthDialog';
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import { _t } from "../../../../languageHandler";
import Modal from "../../../../Modal";
import { SSOAuthEntry } from "../../auth/InteractiveAuthEntryComponents";
import DialogButtons from "../../elements/DialogButtons";
import BaseDialog from "../BaseDialog";
import Spinner from "../../elements/Spinner";
import InteractiveAuthDialog from "../InteractiveAuthDialog";
interface IProps {
accountPassword?: string;
@ -82,8 +82,8 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
logger.log("uploadDeviceSigningKeys advertised no flows!");
return;
}
const canUploadKeysWithPasswordOnly = error.data.flows.some(f => {
return f.stages.length === 1 && f.stages[0] === 'm.login.password';
const canUploadKeysWithPasswordOnly = error.data.flows.some((f) => {
return f.stages.length === 1 && f.stages[0] === "m.login.password";
});
this.setState({
canUploadKeysWithPasswordOnly,
@ -94,9 +94,9 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
private doBootstrapUIAuth = async (makeRequest: (authData: any) => Promise<{}>): Promise<void> => {
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
await makeRequest({
type: 'm.login.password',
type: "m.login.password",
identifier: {
type: 'm.id.user',
type: "m.id.user",
user: MatrixClientPeg.get().getUserId(),
},
// TODO: Remove `user` once servers support proper UIA
@ -170,31 +170,35 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
render() {
let content;
if (this.state.error) {
content = <div>
<p>{ _t("Unable to set up keys") }</p>
<div className="mx_Dialog_buttons">
<DialogButtons primaryButton={_t('Retry')}
onPrimaryButtonClick={this.bootstrapCrossSigning}
onCancel={this.onCancel}
/>
content = (
<div>
<p>{_t("Unable to set up keys")}</p>
<div className="mx_Dialog_buttons">
<DialogButtons
primaryButton={_t("Retry")}
onPrimaryButtonClick={this.bootstrapCrossSigning}
onCancel={this.onCancel}
/>
</div>
</div>
</div>;
);
} else {
content = <div>
<Spinner />
</div>;
content = (
<div>
<Spinner />
</div>
);
}
return (
<BaseDialog className="mx_CreateCrossSigningDialog"
<BaseDialog
className="mx_CreateCrossSigningDialog"
onFinished={this.props.onFinished}
title={_t("Setting up keys")}
hasCancel={false}
fixedWidth={false}
>
<div>
{ content }
</div>
<div>{content}</div>
</BaseDialog>
);
}

View file

@ -15,17 +15,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { IKeyBackupInfo, IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup";
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
import { _t } from '../../../../languageHandler';
import { accessSecretStorage } from '../../../../SecurityManager';
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import { _t } from "../../../../languageHandler";
import { accessSecretStorage } from "../../../../SecurityManager";
import { IDialogProps } from "../IDialogProps";
import Spinner from '../../elements/Spinner';
import Spinner from "../../elements/Spinner";
import DialogButtons from "../../elements/DialogButtons";
import AccessibleButton from "../../elements/AccessibleButton";
import BaseDialog from "../BaseDialog";
@ -33,14 +33,13 @@ import BaseDialog from "../BaseDialog";
enum RestoreType {
Passphrase = "passphrase",
RecoveryKey = "recovery_key",
SecretStorage = "secret_storage"
SecretStorage = "secret_storage",
}
enum ProgressState {
PreFetch = "prefetch",
Fetch = "fetch",
LoadKeys = "load_keys",
}
interface IProps extends IDialogProps {
@ -94,7 +93,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
recoverInfo: null,
recoveryKeyValid: false,
forceRecoveryKey: false,
passPhrase: '',
passPhrase: "",
restoreType: null,
progress: { stage: ProgressState.PreFetch },
};
@ -146,12 +145,16 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
// We do still restore the key backup: we must ensure that the key backup key
// is the right one and restoring it is currently the only way we can do this.
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword(
this.state.passPhrase, undefined, undefined, this.state.backupInfo,
this.state.passPhrase,
undefined,
undefined,
this.state.backupInfo,
{ progressCallback: this.progressCallback },
);
if (this.props.keyCallback) {
const key = await MatrixClientPeg.get().keyBackupKeyFromPassword(
this.state.passPhrase, this.state.backupInfo,
this.state.passPhrase,
this.state.backupInfo,
);
this.props.keyCallback(key);
}
@ -183,7 +186,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
});
try {
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey(
this.state.recoveryKey, undefined, undefined, this.state.backupInfo,
this.state.recoveryKey,
undefined,
undefined,
this.state.backupInfo,
{ progressCallback: this.progressCallback },
);
if (this.props.keyCallback) {
@ -223,7 +229,9 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
// `accessSecretStorage` may prompt for storage access as needed.
await accessSecretStorage(async () => {
await MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
this.state.backupInfo, undefined, undefined,
this.state.backupInfo,
undefined,
undefined,
{ progressCallback: this.progressCallback },
);
});
@ -243,8 +251,8 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
if (!backupInfo) return false;
try {
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithCache(
undefined, /* targetRoomId */
undefined, /* targetSessionId */
undefined /* targetRoomId */,
undefined /* targetSessionId */,
backupInfo,
{ progressCallback: this.progressCallback },
);
@ -301,12 +309,11 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
}
public render(): JSX.Element {
const backupHasPassphrase = (
const backupHasPassphrase =
this.state.backupInfo &&
this.state.backupInfo.auth_data &&
this.state.backupInfo.auth_data.private_key_salt &&
this.state.backupInfo.auth_data.private_key_iterations
);
this.state.backupInfo.auth_data.private_key_iterations;
let content;
let title;
@ -321,10 +328,12 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
} else if (this.state.progress.stage === ProgressState.PreFetch) {
details = _t("Fetching keys from server...");
}
content = <div>
<div>{ details }</div>
<Spinner />
</div>;
content = (
<div>
<div>{details}</div>
<Spinner />
</div>
);
} else if (this.state.loadError) {
title = _t("Error");
content = _t("Unable to load backup status");
@ -332,20 +341,28 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
if (this.state.restoreError.errcode === MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY) {
if (this.state.restoreType === RestoreType.RecoveryKey) {
title = _t("Security Key mismatch");
content = <div>
<p>{ _t(
"Backup could not be decrypted with this Security Key: " +
"please verify that you entered the correct Security Key.",
) }</p>
</div>;
content = (
<div>
<p>
{_t(
"Backup could not be decrypted with this Security Key: " +
"please verify that you entered the correct Security Key.",
)}
</p>
</div>
);
} else {
title = _t("Incorrect Security Phrase");
content = <div>
<p>{ _t(
"Backup could not be decrypted with this Security Phrase: " +
"please verify that you entered the correct Security Phrase.",
) }</p>
</div>;
content = (
<div>
<p>
{_t(
"Backup could not be decrypted with this Security Phrase: " +
"please verify that you entered the correct Security Phrase.",
)}
</p>
</div>
);
}
} else {
title = _t("Error");
@ -358,69 +375,85 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
title = _t("Keys restored");
let failedToDecrypt;
if (this.state.recoverInfo.total > this.state.recoverInfo.imported) {
failedToDecrypt = <p>{ _t(
"Failed to decrypt %(failedCount)s sessions!",
{ failedCount: this.state.recoverInfo.total - this.state.recoverInfo.imported },
) }</p>;
failedToDecrypt = (
<p>
{_t("Failed to decrypt %(failedCount)s sessions!", {
failedCount: this.state.recoverInfo.total - this.state.recoverInfo.imported,
})}
</p>
);
}
content = <div>
<p>{ _t("Successfully restored %(sessionCount)s keys", { sessionCount: this.state.recoverInfo.imported }) }</p>
{ failedToDecrypt }
<DialogButtons primaryButton={_t('OK')}
onPrimaryButtonClick={this.onDone}
hasCancel={false}
focus={true}
/>
</div>;
content = (
<div>
<p>
{_t("Successfully restored %(sessionCount)s keys", {
sessionCount: this.state.recoverInfo.imported,
})}
</p>
{failedToDecrypt}
<DialogButtons
primaryButton={_t("OK")}
onPrimaryButtonClick={this.onDone}
hasCancel={false}
focus={true}
/>
</div>
);
} else if (backupHasPassphrase && !this.state.forceRecoveryKey) {
title = _t("Enter Security Phrase");
content = <div>
<p>{ _t(
"<b>Warning</b>: you should only set up key backup " +
"from a trusted computer.", {},
{ b: sub => <b>{ sub }</b> },
) }</p>
<p>{ _t(
"Access your secure message history and set up secure " +
"messaging by entering your Security Phrase.",
) }</p>
content = (
<div>
<p>
{_t(
"<b>Warning</b>: you should only set up key backup " + "from a trusted computer.",
{},
{ b: (sub) => <b>{sub}</b> },
)}
</p>
<p>
{_t(
"Access your secure message history and set up secure " +
"messaging by entering your Security Phrase.",
)}
</p>
<form className="mx_RestoreKeyBackupDialog_primaryContainer">
<input type="password"
className="mx_RestoreKeyBackupDialog_passPhraseInput"
onChange={this.onPassPhraseChange}
value={this.state.passPhrase}
autoFocus={true}
/>
<DialogButtons
primaryButton={_t('Next')}
onPrimaryButtonClick={this.onPassPhraseNext}
primaryIsSubmit={true}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
/>
</form>
{ _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
kind="link_inline"
onClick={this.onUseRecoveryKeyClick}
>
{ s }
</AccessibleButton>,
button2: s => <AccessibleButton
kind="link_inline"
onClick={this.onResetRecoveryClick}
>
{ s }
</AccessibleButton>,
}) }
</div>;
<form className="mx_RestoreKeyBackupDialog_primaryContainer">
<input
type="password"
className="mx_RestoreKeyBackupDialog_passPhraseInput"
onChange={this.onPassPhraseChange}
value={this.state.passPhrase}
autoFocus={true}
/>
<DialogButtons
primaryButton={_t("Next")}
onPrimaryButtonClick={this.onPassPhraseNext}
primaryIsSubmit={true}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
/>
</form>
{_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 kind="link_inline" onClick={this.onUseRecoveryKeyClick}>
{s}
</AccessibleButton>
),
button2: (s) => (
<AccessibleButton kind="link_inline" onClick={this.onResetRecoveryClick}>
{s}
</AccessibleButton>
),
},
)}
</div>
);
} else {
title = _t("Enter Security Key");
@ -428,65 +461,73 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
if (this.state.recoveryKey.length === 0) {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus" />;
} else if (this.state.recoveryKeyValid) {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
{ "\uD83D\uDC4D " }{ _t("This looks like a valid Security Key!") }
</div>;
keyStatus = (
<div className="mx_RestoreKeyBackupDialog_keyStatus">
{"\uD83D\uDC4D "}
{_t("This looks like a valid Security Key!")}
</div>
);
} else {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
{ "\uD83D\uDC4E " }{ _t("Not a valid Security Key") }
</div>;
keyStatus = (
<div className="mx_RestoreKeyBackupDialog_keyStatus">
{"\uD83D\uDC4E "}
{_t("Not a valid Security Key")}
</div>
);
}
content = <div>
<p>{ _t(
"<b>Warning</b>: You should only set up key backup " +
"from a trusted computer.", {},
{ b: sub => <b>{ sub }</b> },
) }</p>
<p>{ _t(
"Access your secure message history and set up secure " +
"messaging by entering your Security Key.",
) }</p>
content = (
<div>
<p>
{_t(
"<b>Warning</b>: You should only set up key backup " + "from a trusted computer.",
{},
{ b: (sub) => <b>{sub}</b> },
)}
</p>
<p>
{_t(
"Access your secure message history and set up secure " +
"messaging by entering your Security Key.",
)}
</p>
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
<input className="mx_RestoreKeyBackupDialog_recoveryKeyInput"
onChange={this.onRecoveryKeyChange}
value={this.state.recoveryKey}
autoFocus={true}
/>
{ keyStatus }
<DialogButtons primaryButton={_t('Next')}
onPrimaryButtonClick={this.onRecoveryKeyNext}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
primaryDisabled={!this.state.recoveryKeyValid}
/>
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
<input
className="mx_RestoreKeyBackupDialog_recoveryKeyInput"
onChange={this.onRecoveryKeyChange}
value={this.state.recoveryKey}
autoFocus={true}
/>
{keyStatus}
<DialogButtons
primaryButton={_t("Next")}
onPrimaryButtonClick={this.onRecoveryKeyNext}
hasCancel={true}
onCancel={this.onCancel}
focus={false}
primaryDisabled={!this.state.recoveryKeyValid}
/>
</div>
{_t(
"If you've forgotten your Security Key you can " +
"<button>set up new recovery options</button>",
{},
{
button: (s) => (
<AccessibleButton kind="link_inline" onClick={this.onResetRecoveryClick}>
{s}
</AccessibleButton>
),
},
)}
</div>
{ _t(
"If you've forgotten your Security Key you can "+
"<button>set up new recovery options</button>",
{},
{
button: s => <AccessibleButton
kind="link_inline"
onClick={this.onResetRecoveryClick}
>
{ s }
</AccessibleButton>,
},
) }
</div>;
);
}
return (
<BaseDialog className='mx_RestoreKeyBackupDialog'
onFinished={this.props.onFinished}
title={title}
>
<div className='mx_RestoreKeyBackupDialog_content'>
{ content }
</div>
<BaseDialog className="mx_RestoreKeyBackupDialog" onFinished={this.props.onFinished} title={title}>
<div className="mx_RestoreKeyBackupDialog_content">{content}</div>
</BaseDialog>
);
}

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody';
import BaseDialog from '../BaseDialog';
import { _t } from '../../../../languageHandler';
import { SetupEncryptionStore, Phase } from '../../../../stores/SetupEncryptionStore';
import SetupEncryptionBody from "../../../structures/auth/SetupEncryptionBody";
import BaseDialog from "../BaseDialog";
import { _t } from "../../../../languageHandler";
import { SetupEncryptionStore, Phase } from "../../../../stores/SetupEncryptionStore";
import { IDialogProps } from "../IDialogProps";
function iconFromPhase(phase: Phase) {
@ -58,12 +58,14 @@ export default class SetupEncryptionDialog extends React.Component<IProps, IStat
};
public render() {
return <BaseDialog
headerImage={this.state.icon}
onFinished={this.props.onFinished}
title={_t("Verify this session")}
>
<SetupEncryptionBody onFinished={this.props.onFinished} />
</BaseDialog>;
return (
<BaseDialog
headerImage={this.state.icon}
onFinished={this.props.onFinished}
title={_t("Verify this session")}
>
<SetupEncryptionBody onFinished={this.props.onFinished} />
</BaseDialog>
);
}
}

View file

@ -27,19 +27,23 @@ interface OptionProps extends ComponentProps<typeof RovingAccessibleButton> {
export const Option: React.FC<OptionProps> = ({ inputRef, children, endAdornment, className, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleButton
{...props}
className={classNames(className, "mx_SpotlightDialog_option")}
onFocus={onFocus}
inputRef={ref}
tabIndex={-1}
aria-selected={isActive}
role="option"
>
{ children }
<div className="mx_SpotlightDialog_option--endAdornment">
<kbd className="mx_SpotlightDialog_enterPrompt" aria-hidden></kbd>
{ endAdornment }
</div>
</AccessibleButton>;
return (
<AccessibleButton
{...props}
className={classNames(className, "mx_SpotlightDialog_option")}
onFocus={onFocus}
inputRef={ref}
tabIndex={-1}
aria-selected={isActive}
role="option"
>
{children}
<div className="mx_SpotlightDialog_option--endAdornment">
<kbd className="mx_SpotlightDialog_enterPrompt" aria-hidden>
</kbd>
{endAdornment}
</div>
</AccessibleButton>
);
};

View file

@ -32,14 +32,13 @@ interface Props {
}
export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
let name = room.name
|| getDisplayAliasForAliasSet(room.canonical_alias ?? "", room.aliases ?? [])
|| _t('Unnamed room');
let name =
room.name || getDisplayAliasForAliasSet(room.canonical_alias ?? "", room.aliases ?? []) || _t("Unnamed room");
if (name.length > MAX_NAME_LENGTH) {
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
}
let topic = room.topic || '';
let topic = room.topic || "";
// Additional truncation based on line numbers is done via CSS,
// but to ensure that the DOM is not polluted with a huge string
// we give it a hard limit before rendering.
@ -50,18 +49,20 @@ export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsI
return (
<div className="mx_SpotlightDialog_result_publicRoomDetails">
<div className="mx_SpotlightDialog_result_publicRoomHeader">
<span id={labelId} className="mx_SpotlightDialog_result_publicRoomName">{ name }</span>
<span id={labelId} className="mx_SpotlightDialog_result_publicRoomName">
{name}
</span>
<span id={descriptionId} className="mx_SpotlightDialog_result_publicRoomAlias">
{ room.canonical_alias ?? room.room_id }
{room.canonical_alias ?? room.room_id}
</span>
</div>
<div id={detailsId} className="mx_SpotlightDialog_result_publicRoomDescription">
<span className="mx_SpotlightDialog_result_publicRoomMemberCount">
{ _t("%(count)s Members", {
{_t("%(count)s Members", {
count: room.num_joined_members,
}) }
})}
</span>
{ topic && (
{topic && (
<>
&nbsp;·&nbsp;
<span
@ -69,7 +70,7 @@ export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsI
dangerouslySetInnerHTML={{ __html: linkifyAndSanitizeHtml(topic) }}
/>
</>
) }
)}
</div>
</div>
);

View file

@ -41,27 +41,33 @@ export function RoomResultContextMenus({ room }: Props) {
let generalMenu: JSX.Element;
if (generalMenuPosition !== null) {
if (room.isSpaceRoom()) {
generalMenu = <SpaceContextMenu
{...contextMenuBelow(generalMenuPosition)}
space={room}
onFinished={() => setGeneralMenuPosition(null)}
/>;
generalMenu = (
<SpaceContextMenu
{...contextMenuBelow(generalMenuPosition)}
space={room}
onFinished={() => setGeneralMenuPosition(null)}
/>
);
} else {
generalMenu = <RoomGeneralContextMenu
{...contextMenuBelow(generalMenuPosition)}
room={room}
onFinished={() => setGeneralMenuPosition(null)}
/>;
generalMenu = (
<RoomGeneralContextMenu
{...contextMenuBelow(generalMenuPosition)}
room={room}
onFinished={() => setGeneralMenuPosition(null)}
/>
);
}
}
let notificationMenu: JSX.Element;
if (notificationMenuPosition !== null) {
notificationMenu = <RoomNotificationContextMenu
{...contextMenuBelow(notificationMenuPosition)}
room={room}
onFinished={() => setNotificationMenuPosition(null)}
/>;
notificationMenu = (
<RoomNotificationContextMenu
{...contextMenuBelow(notificationMenuPosition)}
room={room}
onFinished={() => setNotificationMenuPosition(null)}
/>
);
}
const notificationMenuClasses = classNames("mx_SpotlightDialog_option--notifications", {
@ -86,7 +92,7 @@ export function RoomResultContextMenus({ room }: Props) {
title={room.isSpaceRoom() ? _t("Space options") : _t("Room options")}
isExpanded={generalMenuPosition !== null}
/>
{ !room.isSpaceRoom() && (
{!room.isSpaceRoom() && (
<ContextMenuTooltipButton
className={notificationMenuClasses}
onClick={(ev: ButtonEvent) => {
@ -99,9 +105,9 @@ export function RoomResultContextMenus({ room }: Props) {
title={_t("Notification options")}
isExpanded={notificationMenuPosition !== null}
/>
) }
{ generalMenu }
{ notificationMenu }
)}
{generalMenu}
{notificationMenu}
</Fragment>
);
}

File diff suppressed because it is too large Load diff

View file

@ -27,13 +27,15 @@ interface TooltipOptionProps extends ComponentProps<typeof RovingAccessibleToolt
export const TooltipOption: React.FC<TooltipOptionProps> = ({ inputRef, className, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleTooltipButton
{...props}
className={classNames(className, "mx_SpotlightDialog_option")}
onFocus={onFocus}
inputRef={ref}
tabIndex={-1}
aria-selected={isActive}
role="option"
/>;
return (
<AccessibleTooltipButton
{...props}
className={classNames(className, "mx_SpotlightDialog_option")}
onFocus={onFocus}
inputRef={ref}
tabIndex={-1}
aria-selected={isActive}
role="option"
/>
);
};