Fix merge conflict
This commit is contained in:
commit
a96169e80e
74 changed files with 506 additions and 438 deletions
|
@ -72,7 +72,7 @@ const CategoryRoomList = React.createClass({
|
|||
placeholder: _t("Room name or alias"),
|
||||
button: _t("Add to summary"),
|
||||
pickerType: 'room',
|
||||
validAddressTypes: ['mx'],
|
||||
validAddressTypes: ['mx-room-id'],
|
||||
groupId: this.props.groupId,
|
||||
onFinished: (success, addrs) => {
|
||||
if (!success) return;
|
||||
|
@ -106,9 +106,9 @@ const CategoryRoomList = React.createClass({
|
|||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const addButton = this.props.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddRoomsClicked}>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="64" height="64"/>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="64" height="64" />
|
||||
<div className="mx_GroupView_featuredThings_addButton_label">
|
||||
{_t('Add a Room')}
|
||||
{ _t('Add a Room') }
|
||||
</div>
|
||||
</AccessibleButton>) : <div />;
|
||||
|
||||
|
@ -117,17 +117,19 @@ const CategoryRoomList = React.createClass({
|
|||
key={r.room_id}
|
||||
groupId={this.props.groupId}
|
||||
editing={this.props.editing}
|
||||
summaryInfo={r}/>;
|
||||
summaryInfo={r} />;
|
||||
});
|
||||
|
||||
let catHeader = <div />;
|
||||
if (this.props.category && this.props.category.profile) {
|
||||
catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>;
|
||||
catHeader = <div className="mx_GroupView_featuredThings_category">
|
||||
{ this.props.category.profile.name }
|
||||
</div>;
|
||||
}
|
||||
return <div className="mx_GroupView_featuredThings_container">
|
||||
{catHeader}
|
||||
{roomNodes}
|
||||
{addButton}
|
||||
{ catHeader }
|
||||
{ roomNodes }
|
||||
{ addButton }
|
||||
</div>;
|
||||
},
|
||||
});
|
||||
|
@ -179,20 +181,26 @@ const FeaturedRoom = React.createClass({
|
|||
render: function() {
|
||||
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
|
||||
|
||||
const roomName = this.props.summaryInfo.profile.name ||
|
||||
this.props.summaryInfo.profile.canonical_alias ||
|
||||
_t("Unnamed Room");
|
||||
|
||||
const oobData = {
|
||||
roomId: this.props.summaryInfo.room_id,
|
||||
avatarUrl: this.props.summaryInfo.profile.avatar_url,
|
||||
name: this.props.summaryInfo.profile.name,
|
||||
name: roomName,
|
||||
};
|
||||
|
||||
let permalink = null;
|
||||
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
|
||||
permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias;
|
||||
}
|
||||
|
||||
let roomNameNode = null;
|
||||
if (permalink) {
|
||||
roomNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.profile.name}</a>;
|
||||
roomNameNode = <a href={permalink} onClick={this.onClick} >{ roomName }</a>;
|
||||
} else {
|
||||
roomNameNode = <span>{this.props.summaryInfo.profile.name}</span>;
|
||||
roomNameNode = <span>{ roomName }</span>;
|
||||
}
|
||||
|
||||
const deleteButton = this.props.editing ?
|
||||
|
@ -202,13 +210,13 @@ const FeaturedRoom = React.createClass({
|
|||
width="14"
|
||||
height="14"
|
||||
alt="Delete"
|
||||
onClick={this.onDeleteClicked}/>
|
||||
onClick={this.onDeleteClicked} />
|
||||
: <div />;
|
||||
|
||||
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
|
||||
<RoomAvatar oobData={oobData} width={64} height={64} />
|
||||
<div className="mx_GroupView_featuredThing_name">{roomNameNode}</div>
|
||||
{deleteButton}
|
||||
<div className="mx_GroupView_featuredThing_name">{ roomNameNode }</div>
|
||||
{ deleteButton }
|
||||
</AccessibleButton>;
|
||||
},
|
||||
});
|
||||
|
@ -237,8 +245,9 @@ const RoleUserList = React.createClass({
|
|||
description: _t("Who would you like to add to this summary?"),
|
||||
placeholder: _t("Name or matrix ID"),
|
||||
button: _t("Add to summary"),
|
||||
validAddressTypes: ['mx'],
|
||||
validAddressTypes: ['mx-user-id'],
|
||||
groupId: this.props.groupId,
|
||||
shouldOmitSelf: false,
|
||||
onFinished: (success, addrs) => {
|
||||
if (!success) return;
|
||||
const errorList = [];
|
||||
|
@ -271,9 +280,9 @@ const RoleUserList = React.createClass({
|
|||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const addButton = this.props.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="64" height="64"/>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="64" height="64" />
|
||||
<div className="mx_GroupView_featuredThings_addButton_label">
|
||||
{_t('Add a User')}
|
||||
{ _t('Add a User') }
|
||||
</div>
|
||||
</AccessibleButton>) : <div />;
|
||||
const userNodes = this.props.users.map((u) => {
|
||||
|
@ -281,16 +290,16 @@ const RoleUserList = React.createClass({
|
|||
key={u.user_id}
|
||||
summaryInfo={u}
|
||||
editing={this.props.editing}
|
||||
groupId={this.props.groupId}/>;
|
||||
groupId={this.props.groupId} />;
|
||||
});
|
||||
let roleHeader = <div />;
|
||||
if (this.props.role && this.props.role.profile) {
|
||||
roleHeader = <div className="mx_GroupView_featuredThings_category">{this.props.role.profile.name}</div>;
|
||||
roleHeader = <div className="mx_GroupView_featuredThings_category">{ this.props.role.profile.name }</div>;
|
||||
}
|
||||
return <div className="mx_GroupView_featuredThings_container">
|
||||
{roleHeader}
|
||||
{userNodes}
|
||||
{addButton}
|
||||
{ roleHeader }
|
||||
{ userNodes }
|
||||
{ addButton }
|
||||
</div>;
|
||||
},
|
||||
});
|
||||
|
@ -342,7 +351,7 @@ const FeaturedUser = React.createClass({
|
|||
const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
|
||||
|
||||
const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
|
||||
const userNameNode = <a href={permalink} onClick={this.onClick}>{name}</a>;
|
||||
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
||||
const httpUrl = MatrixClientPeg.get()
|
||||
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
|
||||
|
||||
|
@ -353,13 +362,13 @@ const FeaturedUser = React.createClass({
|
|||
width="14"
|
||||
height="14"
|
||||
alt="Delete"
|
||||
onClick={this.onDeleteClicked}/>
|
||||
onClick={this.onDeleteClicked} />
|
||||
: <div />;
|
||||
|
||||
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
|
||||
<BaseAvatar name={name} url={httpUrl} width={64} height={64} />
|
||||
<div className="mx_GroupView_featuredThing_name">{userNameNode}</div>
|
||||
{deleteButton}
|
||||
<div className="mx_GroupView_featuredThing_name">{ userNameNode }</div>
|
||||
{ deleteButton }
|
||||
</AccessibleButton>;
|
||||
},
|
||||
});
|
||||
|
@ -625,7 +634,7 @@ export default React.createClass({
|
|||
const defaultCategoryNode = <CategoryRoomList
|
||||
rooms={defaultCategoryRooms}
|
||||
groupId={this.props.groupId}
|
||||
editing={this.state.editing}/>;
|
||||
editing={this.state.editing} />;
|
||||
const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => {
|
||||
const cat = summary.rooms_section.categories[catId];
|
||||
return <CategoryRoomList
|
||||
|
@ -633,15 +642,15 @@ export default React.createClass({
|
|||
rooms={categoryRooms[catId]}
|
||||
category={cat}
|
||||
groupId={this.props.groupId}
|
||||
editing={this.state.editing}/>;
|
||||
editing={this.state.editing} />;
|
||||
});
|
||||
|
||||
return <div className="mx_GroupView_featuredThings">
|
||||
<div className="mx_GroupView_featuredThings_header">
|
||||
{_t('Featured Rooms:')}
|
||||
{ _t('Featured Rooms:') }
|
||||
</div>
|
||||
{defaultCategoryNode}
|
||||
{categoryRoomNodes}
|
||||
{ defaultCategoryNode }
|
||||
{ categoryRoomNodes }
|
||||
</div>;
|
||||
},
|
||||
|
||||
|
@ -666,7 +675,7 @@ export default React.createClass({
|
|||
const noRoleNode = <RoleUserList
|
||||
users={noRoleUsers}
|
||||
groupId={this.props.groupId}
|
||||
editing={this.state.editing}/>;
|
||||
editing={this.state.editing} />;
|
||||
const roleUserNodes = Object.keys(roleUsers).map((roleId) => {
|
||||
const role = summary.users_section.roles[roleId];
|
||||
return <RoleUserList
|
||||
|
@ -674,15 +683,15 @@ export default React.createClass({
|
|||
users={roleUsers[roleId]}
|
||||
role={role}
|
||||
groupId={this.props.groupId}
|
||||
editing={this.state.editing}/>;
|
||||
editing={this.state.editing} />;
|
||||
});
|
||||
|
||||
return <div className="mx_GroupView_featuredThings">
|
||||
<div className="mx_GroupView_featuredThings_header">
|
||||
{_t('Featured Users:')}
|
||||
{ _t('Featured Users:') }
|
||||
</div>
|
||||
{noRoleNode}
|
||||
{roleUserNodes}
|
||||
{ noRoleNode }
|
||||
{ roleUserNodes }
|
||||
</div>;
|
||||
},
|
||||
|
||||
|
@ -701,18 +710,18 @@ export default React.createClass({
|
|||
|
||||
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_invited">
|
||||
<div className="mx_GroupView_membershipSection_description">
|
||||
{_t("%(inviter)s has invited you to join this group", {inviter: group.inviter.userId})}
|
||||
{ _t("%(inviter)s has invited you to join this group", {inviter: group.inviter.userId}) }
|
||||
</div>
|
||||
<div className="mx_GroupView_membership_buttonContainer">
|
||||
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
|
||||
onClick={this._onAcceptInviteClick}
|
||||
>
|
||||
{_t("Accept")}
|
||||
{ _t("Accept") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
|
||||
onClick={this._onRejectInviteClick}
|
||||
>
|
||||
{_t("Decline")}
|
||||
{ _t("Decline") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
|
@ -733,13 +742,13 @@ export default React.createClass({
|
|||
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
|
||||
onClick={this._onPubliciseOffClick}
|
||||
>
|
||||
{_t("Unpublish")}
|
||||
{ _t("Unpublish") }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
publicisedSection = <div className="mx_GroupView_membershipSubSection">
|
||||
{_t("This group is published on your profile")}
|
||||
{ _t("This group is published on your profile") }
|
||||
<div className="mx_GroupView_membership_buttonContainer">
|
||||
{publicisedButton}
|
||||
{ publicisedButton }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
|
@ -747,13 +756,13 @@ export default React.createClass({
|
|||
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
|
||||
onClick={this._onPubliciseOnClick}
|
||||
>
|
||||
{_t("Publish")}
|
||||
{ _t("Publish") }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
publicisedSection = <div className="mx_GroupView_membershipSubSection">
|
||||
{_t("This group is not published on your profile")}
|
||||
{ _t("This group is not published on your profile") }
|
||||
<div className="mx_GroupView_membership_buttonContainer">
|
||||
{publicisedButton}
|
||||
{ publicisedButton }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
@ -761,17 +770,17 @@ export default React.createClass({
|
|||
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_joined">
|
||||
<div className="mx_GroupView_membershipSubSection">
|
||||
<div className="mx_GroupView_membershipSection_description">
|
||||
{youAreAMemberText}
|
||||
{ youAreAMemberText }
|
||||
</div>
|
||||
<div className="mx_GroupView_membership_buttonContainer">
|
||||
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
|
||||
onClick={this._onLeaveClick}
|
||||
>
|
||||
{_t("Leave")}
|
||||
{ _t("Leave") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
{publicisedSection}
|
||||
{ publicisedSection }
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
@ -811,15 +820,15 @@ export default React.createClass({
|
|||
avatarNode = (
|
||||
<div className="mx_GroupView_avatarPicker">
|
||||
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
|
||||
{avatarImage}
|
||||
{ avatarImage }
|
||||
</label>
|
||||
<div className="mx_GroupView_avatarPicker_edit">
|
||||
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
|
||||
<img src="img/camera.svg"
|
||||
alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
|
||||
alt={_t("Upload avatar")} title={_t("Upload avatar")}
|
||||
width="17" height="15" />
|
||||
</label>
|
||||
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected}/>
|
||||
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -839,13 +848,13 @@ export default React.createClass({
|
|||
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
|
||||
onClick={this._onSaveClick} key="_saveButton"
|
||||
>
|
||||
{_t('Save')}
|
||||
{ _t('Save') }
|
||||
</AccessibleButton>,
|
||||
);
|
||||
rightButtons.push(
|
||||
<AccessibleButton className='mx_GroupView_textButton' onClick={this._onCancelClick} key="_cancelButton">
|
||||
<img src="img/cancel.svg" className='mx_filterFlipColor'
|
||||
width="18" height="18" alt={_t("Cancel")}/>
|
||||
<AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this._onCancelClick} key="_cancelButton">
|
||||
<img src="img/cancel.svg" className="mx_filterFlipColor"
|
||||
width="18" height="18" alt={_t("Cancel")} />
|
||||
</AccessibleButton>,
|
||||
);
|
||||
roomBody = <div>
|
||||
|
@ -853,8 +862,8 @@ export default React.createClass({
|
|||
onChange={this._onLongDescChange}
|
||||
tabIndex="3"
|
||||
/>
|
||||
{this._getFeaturedRoomsNode()}
|
||||
{this._getFeaturedUsersNode()}
|
||||
{ this._getFeaturedRoomsNode() }
|
||||
{ this._getFeaturedUsersNode() }
|
||||
</div>;
|
||||
} else {
|
||||
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||
|
@ -865,41 +874,41 @@ export default React.createClass({
|
|||
/>;
|
||||
if (summary.profile && summary.profile.name) {
|
||||
nameNode = <div>
|
||||
<span>{summary.profile.name}</span>
|
||||
<span>{ summary.profile.name }</span>
|
||||
<span className="mx_GroupView_header_groupid">
|
||||
({this.props.groupId})
|
||||
({ this.props.groupId })
|
||||
</span>
|
||||
</div>;
|
||||
} else {
|
||||
nameNode = <span>{this.props.groupId}</span>;
|
||||
nameNode = <span>{ this.props.groupId }</span>;
|
||||
}
|
||||
shortDescNode = <span>{summary.profile.short_description}</span>;
|
||||
shortDescNode = <span>{ summary.profile.short_description }</span>;
|
||||
|
||||
let description = null;
|
||||
if (summary.profile && summary.profile.long_description) {
|
||||
description = sanitizedHtmlNode(summary.profile.long_description);
|
||||
}
|
||||
roomBody = <div>
|
||||
{this._getMembershipSection()}
|
||||
<div className="mx_GroupView_groupDesc">{description}</div>
|
||||
{this._getFeaturedRoomsNode()}
|
||||
{this._getFeaturedUsersNode()}
|
||||
{ this._getMembershipSection() }
|
||||
<div className="mx_GroupView_groupDesc">{ description }</div>
|
||||
{ this._getFeaturedRoomsNode() }
|
||||
{ this._getFeaturedUsersNode() }
|
||||
</div>;
|
||||
if (summary.user && summary.user.is_privileged) {
|
||||
rightButtons.push(
|
||||
<AccessibleButton className="mx_GroupHeader_button"
|
||||
onClick={this._onEditClick} title={_t("Edit Group")} key="_editButton"
|
||||
>
|
||||
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
|
||||
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16" />
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
if (this.props.collapsedRhs) {
|
||||
rightButtons.push(
|
||||
<AccessibleButton className="mx_GroupHeader_button"
|
||||
onClick={this._onShowRhsClick} title={ _t('Show panel') } key="_maximiseButton"
|
||||
onClick={this._onShowRhsClick} title={_t('Show panel')} key="_maximiseButton"
|
||||
>
|
||||
<TintableSvg src="img/maximise.svg" width="10" height="16"/>
|
||||
<TintableSvg src="img/maximise.svg" width="10" height="16" />
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
|
@ -912,40 +921,40 @@ export default React.createClass({
|
|||
<div className={classnames(headerClasses)}>
|
||||
<div className="mx_GroupView_header_leftCol">
|
||||
<div className="mx_GroupView_header_avatar">
|
||||
{avatarNode}
|
||||
{ avatarNode }
|
||||
</div>
|
||||
<div className="mx_GroupView_header_info">
|
||||
<div className="mx_GroupView_header_name">
|
||||
{nameNode}
|
||||
{ nameNode }
|
||||
</div>
|
||||
<div className="mx_GroupView_header_shortDesc">
|
||||
{shortDescNode}
|
||||
{ shortDescNode }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_GroupView_header_rightCol">
|
||||
{rightButtons}
|
||||
{ rightButtons }
|
||||
</div>
|
||||
</div>
|
||||
{roomBody}
|
||||
{ roomBody }
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.error) {
|
||||
if (this.state.error.httpStatus === 404) {
|
||||
return (
|
||||
<div className="mx_GroupView_error">
|
||||
Group {this.props.groupId} not found
|
||||
Group { this.props.groupId } not found
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let extraText;
|
||||
if (this.state.error.errcode === 'M_UNRECOGNIZED') {
|
||||
extraText = <div>{_t('This Home server does not support groups')}</div>;
|
||||
extraText = <div>{ _t('This Home server does not support groups') }</div>;
|
||||
}
|
||||
return (
|
||||
<div className="mx_GroupView_error">
|
||||
Failed to load {this.props.groupId}
|
||||
{extraText}
|
||||
Failed to load { this.props.groupId }
|
||||
{ extraText }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -853,7 +853,7 @@ module.exports = React.createClass({
|
|||
title: _t("Leave room"),
|
||||
description: (
|
||||
<span>
|
||||
{_t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name})}
|
||||
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
||||
</span>
|
||||
),
|
||||
onFinished: (shouldLeave) => {
|
||||
|
@ -1450,7 +1450,7 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<div className="mx_MatrixChat_splash">
|
||||
<Spinner />
|
||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={this.onLogoutClick}>
|
||||
{ _t('Logout') }
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -39,7 +39,7 @@ const GroupTile = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
return <a onClick={this.onClick} href="#">{this.props.groupId}</a>;
|
||||
return <a onClick={this.onClick} href="#">{ this.props.groupId }</a>;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -90,51 +90,51 @@ export default withMatrixClient(React.createClass({
|
|||
);
|
||||
});
|
||||
content = <div>
|
||||
<div>{_t('You are a member of these groups:')}</div>
|
||||
{groupNodes}
|
||||
<div>{ _t('You are a member of these groups:') }</div>
|
||||
{ groupNodes }
|
||||
</div>;
|
||||
} else if (this.state.error) {
|
||||
content = <div className="mx_MyGroups_error">
|
||||
{_t('Error whilst fetching joined groups')}
|
||||
{ _t('Error whilst fetching joined groups') }
|
||||
</div>;
|
||||
} else {
|
||||
content = <Loader />;
|
||||
}
|
||||
|
||||
return <div className="mx_MyGroups">
|
||||
<SimpleRoomHeader title={ _t("Groups") } />
|
||||
<SimpleRoomHeader title={_t("Groups")} icon="img/icons-groups.svg" />
|
||||
<div className='mx_MyGroups_joinCreateBox'>
|
||||
<div className="mx_MyGroups_createBox">
|
||||
<div className="mx_MyGroups_joinCreateHeader">
|
||||
{_t('Create a new group')}
|
||||
{ _t('Create a new group') }
|
||||
</div>
|
||||
<AccessibleButton className='mx_MyGroups_joinCreateButton' onClick={this._onCreateGroupClick}>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="50" height="50" />
|
||||
</AccessibleButton>
|
||||
{_t(
|
||||
{ _t(
|
||||
'Create a group to represent your community! '+
|
||||
'Define a set of rooms and your own custom homepage '+
|
||||
'to mark out your space in the Matrix universe.',
|
||||
)}
|
||||
) }
|
||||
</div>
|
||||
<div className="mx_MyGroups_joinBox">
|
||||
<div className="mx_MyGroups_joinCreateHeader">
|
||||
{_t('Join an existing group')}
|
||||
{ _t('Join an existing group') }
|
||||
</div>
|
||||
<AccessibleButton className='mx_MyGroups_joinCreateButton' onClick={this._onJoinGroupClick}>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="50" height="50" />
|
||||
</AccessibleButton>
|
||||
{_tJsx(
|
||||
{ _tJsx(
|
||||
'To join an existing group you\'ll have to '+
|
||||
'know its group identifier; this will look '+
|
||||
'something like <i>+example:matrix.org</i>.',
|
||||
/<i>(.*)<\/i>/,
|
||||
(sub) => <i>{sub}</i>,
|
||||
)}
|
||||
(sub) => <i>{ sub }</i>,
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_MyGroups_content">
|
||||
{content}
|
||||
{ content }
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
|
|
@ -52,7 +52,7 @@ const gHVersionLabel = function(repo, token='') {
|
|||
} else {
|
||||
url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
|
||||
}
|
||||
return <a target="_blank" rel="noopener" href={url}>{token}</a>;
|
||||
return <a target="_blank" rel="noopener" href={url}>{ token }</a>;
|
||||
};
|
||||
|
||||
// Enumerate some simple 'flip a bit' UI settings (if any).
|
||||
|
@ -674,7 +674,7 @@ module.exports = React.createClass({
|
|||
<div>
|
||||
<h3>Referral</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{_t("Refer a friend to Riot:")} <a href={href}>{href}</a>
|
||||
{ _t("Refer a friend to Riot:") } <a href={href}>{ href }</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -693,7 +693,7 @@ module.exports = React.createClass({
|
|||
_renderLanguageSetting: function() {
|
||||
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
|
||||
return <div>
|
||||
<label htmlFor="languageSelector">{_t('Interface Language')}</label>
|
||||
<label htmlFor="languageSelector">{ _t('Interface Language') }</label>
|
||||
<LanguageDropdown ref="language" onOptionChange={this.onLanguageChange}
|
||||
className="mx_UserSettings_language"
|
||||
value={this.state.language}
|
||||
|
@ -716,7 +716,7 @@ module.exports = React.createClass({
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{_t('Autocomplete Delay (ms):')}</strong></td>
|
||||
<td><strong>{ _t('Autocomplete Delay (ms):') }</strong></td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
|
@ -737,8 +737,8 @@ module.exports = React.createClass({
|
|||
return <div className="mx_UserSettings_toggle">
|
||||
<input id="urlPreviewsDisabled"
|
||||
type="checkbox"
|
||||
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
||||
onChange={ this._onPreviewsDisabledChanged }
|
||||
defaultChecked={UserSettingsStore.getUrlPreviewsDisabled()}
|
||||
onChange={this._onPreviewsDisabledChanged}
|
||||
/>
|
||||
<label htmlFor="urlPreviewsDisabled">
|
||||
{ _t("Disable inline URL previews by default") }
|
||||
|
@ -759,13 +759,13 @@ module.exports = React.createClass({
|
|||
if (setting.fn) setting.fn(e.target.checked);
|
||||
};
|
||||
|
||||
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
||||
<input id={ setting.id }
|
||||
return <div className="mx_UserSettings_toggle" key={setting.id}>
|
||||
<input id={setting.id}
|
||||
type="checkbox"
|
||||
defaultChecked={ this._syncedSettings[setting.id] }
|
||||
onChange={ onChange }
|
||||
defaultChecked={this._syncedSettings[setting.id]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<label htmlFor={ setting.id }>
|
||||
<label htmlFor={setting.id}>
|
||||
{ _t(setting.label) }
|
||||
</label>
|
||||
</div>;
|
||||
|
@ -784,15 +784,15 @@ module.exports = React.createClass({
|
|||
value: setting.value,
|
||||
});
|
||||
};
|
||||
return <div className="mx_UserSettings_toggle" key={ setting.id + "_" + setting.value }>
|
||||
<input id={ setting.id + "_" + setting.value }
|
||||
return <div className="mx_UserSettings_toggle" key={setting.id + "_" + setting.value}>
|
||||
<input id={setting.id + "_" + setting.value}
|
||||
type="radio"
|
||||
name={ setting.id }
|
||||
value={ setting.value }
|
||||
checked={ this._syncedSettings[setting.id] === setting.value }
|
||||
onChange={ onChange }
|
||||
name={setting.id}
|
||||
value={setting.value}
|
||||
checked={this._syncedSettings[setting.id] === setting.value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<label htmlFor={ setting.id + "_" + setting.value }>
|
||||
<label htmlFor={setting.id + "_" + setting.value}>
|
||||
{ _t(setting.label) }
|
||||
</label>
|
||||
</div>;
|
||||
|
@ -829,10 +829,10 @@ module.exports = React.createClass({
|
|||
<h3>{ _t("Cryptography") }</h3>
|
||||
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
|
||||
<ul>
|
||||
<li><label>{_t("Device ID:")}</label>
|
||||
<span><code>{deviceId}</code></span></li>
|
||||
<li><label>{_t("Device key:")}</label>
|
||||
<span><code><b>{identityKey}</b></code></span></li>
|
||||
<li><label>{ _t("Device ID:") }</label>
|
||||
<span><code>{ deviceId }</code></span></li>
|
||||
<li><label>{ _t("Device key:") }</label>
|
||||
<span><code><b>{ identityKey }</b></code></span></li>
|
||||
</ul>
|
||||
{ importExportButtons }
|
||||
</div>
|
||||
|
@ -851,11 +851,11 @@ module.exports = React.createClass({
|
|||
<h3>{ _t("Ignored Users") }</h3>
|
||||
<div className="mx_UserSettings_section mx_UserSettings_ignoredUsersSection">
|
||||
<ul>
|
||||
{this.state.ignoredUsers.map(function(userId) {
|
||||
{ this.state.ignoredUsers.map(function(userId) {
|
||||
return (<IgnoredUser key={userId}
|
||||
userId={userId}
|
||||
onUnignored={updateHandler}></IgnoredUser>);
|
||||
})}
|
||||
}) }
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -871,13 +871,13 @@ module.exports = React.createClass({
|
|||
if (setting.fn) setting.fn(e.target.checked);
|
||||
};
|
||||
|
||||
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
||||
<input id={ setting.id }
|
||||
return <div className="mx_UserSettings_toggle" key={setting.id}>
|
||||
<input id={setting.id}
|
||||
type="checkbox"
|
||||
defaultChecked={ this._localSettings[setting.id] }
|
||||
onChange={ onChange }
|
||||
defaultChecked={this._localSettings[setting.id]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<label htmlFor={ setting.id }>
|
||||
<label htmlFor={setting.id}>
|
||||
{ _t(setting.label) }
|
||||
</label>
|
||||
</div>;
|
||||
|
@ -887,8 +887,8 @@ module.exports = React.createClass({
|
|||
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
||||
return (
|
||||
<div>
|
||||
<h3>{_t("Devices")}</h3>
|
||||
<DevicesPanel className="mx_UserSettings_section"/>
|
||||
<h3>{ _t("Devices") }</h3>
|
||||
<DevicesPanel className="mx_UserSettings_section" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -903,7 +903,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_UserSettings_section">
|
||||
<p>{ _t("Found a bug?") }</p>
|
||||
<button className="mx_UserSettings_button danger"
|
||||
onClick={this._onBugReportClicked}>{_t('Report it')}
|
||||
onClick={this._onBugReportClicked}>{ _t('Report it') }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -911,13 +911,13 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_renderAnalyticsControl: function() {
|
||||
if (!SdkConfig.get().piwik) return <div/>;
|
||||
if (!SdkConfig.get().piwik) return <div />;
|
||||
|
||||
return <div>
|
||||
<h3>{ _t('Analytics') }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{_t('Riot collects anonymous analytics to allow us to improve the application.')}
|
||||
{ANALYTICS_SETTINGS_LABELS.map( this._renderLocalSetting )}
|
||||
{ _t('Riot collects anonymous analytics to allow us to improve the application.') }
|
||||
{ ANALYTICS_SETTINGS_LABELS.map( this._renderLocalSetting ) }
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
@ -947,10 +947,10 @@ module.exports = React.createClass({
|
|||
type="checkbox"
|
||||
id={feature.id}
|
||||
name={feature.id}
|
||||
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
|
||||
onChange={ onChange }
|
||||
defaultChecked={UserSettingsStore.isFeatureEnabled(feature.id)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<label htmlFor={feature.id}>{feature.name}</label>
|
||||
<label htmlFor={feature.id}>{ feature.name }</label>
|
||||
</div>);
|
||||
});
|
||||
|
||||
|
@ -964,7 +964,7 @@ module.exports = React.createClass({
|
|||
<h3>{ _t("Labs") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<p>{ _t("These are experimental features that may break in unexpected ways") }. { _t("Use with caution") }.</p>
|
||||
{features}
|
||||
{ features }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -997,10 +997,10 @@ module.exports = React.createClass({
|
|||
const platform = PlatformPeg.get();
|
||||
if ('canSelfUpdate' in platform && platform.canSelfUpdate() && 'startUpdateCheck' in platform) {
|
||||
return <div>
|
||||
<h3>{_t('Updates')}</h3>
|
||||
<h3>{ _t('Updates') }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<AccessibleButton className="mx_UserSettings_button" onClick={platform.startUpdateCheck}>
|
||||
{_t('Check for update')}
|
||||
{ _t('Check for update') }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
|
@ -1026,7 +1026,7 @@ module.exports = React.createClass({
|
|||
reject = (
|
||||
<AccessibleButton className="mx_UserSettings_button danger"
|
||||
onClick={onClick}>
|
||||
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
|
||||
{ _t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length}) }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
@ -1034,7 +1034,7 @@ module.exports = React.createClass({
|
|||
return <div>
|
||||
<h3>{ _t("Bulk Options") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{reject}
|
||||
{ reject }
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
@ -1052,7 +1052,7 @@ module.exports = React.createClass({
|
|||
defaultChecked={settings['auto-launch']}
|
||||
onChange={this._onAutoLaunchChanged}
|
||||
/>
|
||||
<label htmlFor="auto-launch">{_t('Start automatically after system login')}</label>
|
||||
<label htmlFor="auto-launch">{ _t('Start automatically after system login') }</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
|
@ -1064,7 +1064,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_mapWebRtcDevicesToSpans: function(devices) {
|
||||
return devices.map((device) => <span key={device.deviceId}>{device.label}</span>);
|
||||
return devices.map((device) => <span key={device.deviceId}>{ device.label }</span>);
|
||||
},
|
||||
|
||||
_setAudioInput: function(deviceId) {
|
||||
|
@ -1100,15 +1100,15 @@ module.exports = React.createClass({
|
|||
if (this.state.mediaDevices === false) {
|
||||
return (
|
||||
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
|
||||
{_t('Missing Media Permissions, click here to request.')}
|
||||
{ _t('Missing Media Permissions, click here to request.') }
|
||||
</p>
|
||||
);
|
||||
} else if (!this.state.mediaDevices) return;
|
||||
|
||||
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||
|
||||
let microphoneDropdown = <p>{_t('No Microphones detected')}</p>;
|
||||
let webcamDropdown = <p>{_t('No Webcams detected')}</p>;
|
||||
let microphoneDropdown = <p>{ _t('No Microphones detected') }</p>;
|
||||
let webcamDropdown = <p>{ _t('No Webcams detected') }</p>;
|
||||
|
||||
const defaultOption = {
|
||||
deviceId: '',
|
||||
|
@ -1125,12 +1125,12 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
microphoneDropdown = <div>
|
||||
<h4>{_t('Microphone')}</h4>
|
||||
<h4>{ _t('Microphone') }</h4>
|
||||
<Dropdown
|
||||
className="mx_UserSettings_webRtcDevices_dropdown"
|
||||
value={this.state.activeAudioInput || defaultInput}
|
||||
onOptionChange={this._setAudioInput}>
|
||||
{this._mapWebRtcDevicesToSpans(audioInputs)}
|
||||
{ this._mapWebRtcDevicesToSpans(audioInputs) }
|
||||
</Dropdown>
|
||||
</div>;
|
||||
}
|
||||
|
@ -1145,25 +1145,25 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
webcamDropdown = <div>
|
||||
<h4>{_t('Camera')}</h4>
|
||||
<h4>{ _t('Camera') }</h4>
|
||||
<Dropdown
|
||||
className="mx_UserSettings_webRtcDevices_dropdown"
|
||||
value={this.state.activeVideoInput || defaultInput}
|
||||
onOptionChange={this._setVideoInput}>
|
||||
{this._mapWebRtcDevicesToSpans(videoInputs)}
|
||||
{ this._mapWebRtcDevicesToSpans(videoInputs) }
|
||||
</Dropdown>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div>
|
||||
{microphoneDropdown}
|
||||
{webcamDropdown}
|
||||
{ microphoneDropdown }
|
||||
{ webcamDropdown }
|
||||
</div>;
|
||||
},
|
||||
|
||||
_renderWebRtcSettings: function() {
|
||||
return <div>
|
||||
<h3>{_t('VoIP')}</h3>
|
||||
<h3>{ _t('VoIP') }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{ WEBRTC_SETTINGS_LABELS.map(this._renderLocalSetting) }
|
||||
{ this._renderWebRtcDeviceSettings() }
|
||||
|
@ -1229,7 +1229,7 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
|
||||
<div className="mx_UserSettings_profileLabelCell">
|
||||
<label htmlFor={id}>{this.nameForMedium(val.medium)}</label>
|
||||
<label htmlFor={id}>{ this.nameForMedium(val.medium) }</label>
|
||||
</div>
|
||||
<div className="mx_UserSettings_profileInputCell">
|
||||
<input type="text" key={val.address} id={id}
|
||||
|
@ -1237,7 +1237,7 @@ module.exports = React.createClass({
|
|||
/>
|
||||
</div>
|
||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") }
|
||||
<img src="img/cancel-small.svg" width="14" height="14" alt={_t("Remove")}
|
||||
onClick={onRemoveClick} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1250,16 +1250,16 @@ module.exports = React.createClass({
|
|||
addEmailSection = (
|
||||
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
|
||||
<div className="mx_UserSettings_profileLabelCell">
|
||||
<label>{_t('Email')}</label>
|
||||
<label>{ _t('Email') }</label>
|
||||
</div>
|
||||
<div className="mx_UserSettings_profileInputCell">
|
||||
<EditableText
|
||||
ref="add_email_input"
|
||||
className="mx_UserSettings_editable"
|
||||
placeholderClassName="mx_UserSettings_threepidPlaceholder"
|
||||
placeholder={ _t("Add email address") }
|
||||
blurToCancel={ false }
|
||||
onValueChanged={ this._onAddEmailEditFinished } />
|
||||
placeholder={_t("Add email address")}
|
||||
blurToCancel={false}
|
||||
onValueChanged={this._onAddEmailEditFinished} />
|
||||
</div>
|
||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<img src="img/plus.svg" width="14" height="14" alt={_t("Add")} onClick={this._addEmail} />
|
||||
|
@ -1307,8 +1307,8 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<div className="mx_UserSettings">
|
||||
<SimpleRoomHeader
|
||||
title={ _t("Settings") }
|
||||
onCancelClick={ this.props.onClose }
|
||||
title={_t("Settings")}
|
||||
onCancelClick={this.props.onClose}
|
||||
/>
|
||||
|
||||
<GeminiScrollbar className="mx_UserSettings_body"
|
||||
|
@ -1326,21 +1326,21 @@ module.exports = React.createClass({
|
|||
<ChangeDisplayName />
|
||||
</div>
|
||||
</div>
|
||||
{threepidsSection}
|
||||
{ threepidsSection }
|
||||
</div>
|
||||
|
||||
<div className="mx_UserSettings_avatarPicker">
|
||||
<div onClick={ this.onAvatarPickerClick }>
|
||||
<div onClick={this.onAvatarPickerClick}>
|
||||
<ChangeAvatar ref="changeAvatar" initialAvatarUrl={avatarUrl}
|
||||
showUploadSection={false} className="mx_UserSettings_avatarPicker_img"/>
|
||||
showUploadSection={false} className="mx_UserSettings_avatarPicker_img" />
|
||||
</div>
|
||||
<div className="mx_UserSettings_avatarPicker_edit">
|
||||
<label htmlFor="avatarInput" ref="file_label">
|
||||
<img src="img/camera.svg" className="mx_filterFlipColor"
|
||||
alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
|
||||
alt={_t("Upload avatar")} title={_t("Upload avatar")}
|
||||
width="17" height="15" />
|
||||
</label>
|
||||
<input id="avatarInput" type="file" onChange={this.onAvatarSelected}/>
|
||||
<input id="avatarInput" type="file" onChange={this.onAvatarSelected} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1357,37 +1357,37 @@ module.exports = React.createClass({
|
|||
</div> : null
|
||||
}
|
||||
|
||||
{accountJsx}
|
||||
{ accountJsx }
|
||||
</div>
|
||||
|
||||
{this._renderReferral()}
|
||||
{ this._renderReferral() }
|
||||
|
||||
{notificationArea}
|
||||
{ notificationArea }
|
||||
|
||||
{this._renderUserInterfaceSettings()}
|
||||
{this._renderLabs()}
|
||||
{this._renderWebRtcSettings()}
|
||||
{this._renderDevicesPanel()}
|
||||
{this._renderCryptoInfo()}
|
||||
{this._renderIgnoredUsers()}
|
||||
{this._renderBulkOptions()}
|
||||
{this._renderBugReport()}
|
||||
{ this._renderUserInterfaceSettings() }
|
||||
{ this._renderLabs() }
|
||||
{ this._renderWebRtcSettings() }
|
||||
{ this._renderDevicesPanel() }
|
||||
{ this._renderCryptoInfo() }
|
||||
{ this._renderIgnoredUsers() }
|
||||
{ this._renderBulkOptions() }
|
||||
{ this._renderBugReport() }
|
||||
|
||||
{PlatformPeg.get().isElectron() && this._renderElectronSettings()}
|
||||
{ PlatformPeg.get().isElectron() && this._renderElectronSettings() }
|
||||
|
||||
{this._renderAnalyticsControl()}
|
||||
{ this._renderAnalyticsControl() }
|
||||
|
||||
<h3>{ _t("Advanced") }</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_advanced">
|
||||
{ _t("Logged in as:") } {this._me}
|
||||
{ _t("Logged in as:") } { this._me }
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
{_t('Access Token:')}
|
||||
{ _t('Access Token:') }
|
||||
<span className="mx_UserSettings_advanced_spoiler"
|
||||
onClick={this._showSpoiler}
|
||||
data-spoiler={ MatrixClientPeg.get().getAccessToken() }>
|
||||
data-spoiler={MatrixClientPeg.get().getAccessToken()}>
|
||||
<{ _t("click to reveal") }>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1398,23 +1398,23 @@ module.exports = React.createClass({
|
|||
{ _t("Identity Server is") } { MatrixClientPeg.get().getIdentityServerUrl() }
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
{_t('matrix-react-sdk version:')} {(REACT_SDK_VERSION !== '<local>')
|
||||
{ _t('matrix-react-sdk version:') } { (REACT_SDK_VERSION !== '<local>')
|
||||
? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
|
||||
: REACT_SDK_VERSION
|
||||
}<br/>
|
||||
{_t('riot-web version:')} {(this.state.vectorVersion !== undefined)
|
||||
}<br />
|
||||
{ _t('riot-web version:') } { (this.state.vectorVersion !== undefined)
|
||||
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
|
||||
: 'unknown'
|
||||
}<br/>
|
||||
{ _t("olm version:") } {olmVersionString}<br/>
|
||||
}<br />
|
||||
{ _t("olm version:") } { olmVersionString }<br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this._renderCheckUpdate()}
|
||||
{ this._renderCheckUpdate() }
|
||||
|
||||
{this._renderClearCache()}
|
||||
{ this._renderClearCache() }
|
||||
|
||||
{this._renderDeactivateAccount()}
|
||||
{ this._renderDeactivateAccount() }
|
||||
|
||||
</GeminiScrollbar>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue