mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into luke/groups-inviter-profile
commit
10778e075e
|
@ -25,7 +25,6 @@ const onAction = function(payload) {
|
|||
const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
|
||||
isDialogOpen = true;
|
||||
Modal.createTrackedDialog('Unknown Device Error', '', UnknownDeviceDialog, {
|
||||
devices: payload.err.devices,
|
||||
room: payload.room,
|
||||
onFinished: (r) => {
|
||||
isDialogOpen = false;
|
||||
|
|
|
@ -22,7 +22,7 @@ import MatrixClientPeg from '../../MatrixClientPeg';
|
|||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
||||
import { _t } from '../../languageHandler';
|
||||
import { _t, _td, _tJsx } from '../../languageHandler';
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
import Modal from '../../Modal';
|
||||
import classnames from 'classnames';
|
||||
|
@ -32,6 +32,17 @@ import GroupStore from '../../stores/GroupStore';
|
|||
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||
|
||||
const LONG_DESC_PLACEHOLDER = _td(
|
||||
`<h1>HTML for your community's page</h1>
|
||||
<p>
|
||||
Use the long description to introduce new members to the community, or distribute
|
||||
some important <a href="foo">links</a>
|
||||
</p>
|
||||
<p>
|
||||
You can even use 'img' tags
|
||||
</p>
|
||||
`);
|
||||
|
||||
const RoomSummaryType = PropTypes.shape({
|
||||
room_id: PropTypes.string.isRequired,
|
||||
profile: PropTypes.shape({
|
||||
|
@ -392,6 +403,8 @@ export default React.createClass({
|
|||
|
||||
propTypes: {
|
||||
groupId: PropTypes.string.isRequired,
|
||||
// Whether this is the first time the group admin is viewing the group
|
||||
groupIsNew: PropTypes.bool,
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
|
@ -423,7 +436,7 @@ export default React.createClass({
|
|||
|
||||
componentWillMount: function() {
|
||||
this._changeAvatarComponent = null;
|
||||
this._initGroupStore(this.props.groupId);
|
||||
this._initGroupStore(this.props.groupId, true);
|
||||
|
||||
MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership);
|
||||
},
|
||||
|
@ -450,12 +463,11 @@ export default React.createClass({
|
|||
this.setState({membershipBusy: false});
|
||||
},
|
||||
|
||||
_initGroupStore: function(groupId) {
|
||||
_initGroupStore: function(groupId, firstInit) {
|
||||
const group = MatrixClientPeg.get().getGroup(groupId);
|
||||
if (group && group.inviter && group.inviter.userId) {
|
||||
this._fetchInviterProfile(group.inviter.userId);
|
||||
}
|
||||
|
||||
this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
|
||||
this._groupStore.registerListener(() => {
|
||||
const summary = this._groupStore.getSummary();
|
||||
|
@ -478,6 +490,9 @@ export default React.createClass({
|
|||
),
|
||||
error: null,
|
||||
});
|
||||
if (this.props.groupIsNew && firstInit) {
|
||||
this._onEditClick();
|
||||
}
|
||||
});
|
||||
this._groupStore.on('error', (err) => {
|
||||
this.setState({
|
||||
|
@ -687,6 +702,14 @@ export default React.createClass({
|
|||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
const ToolTipButton = sdk.getComponent('elements.ToolTipButton');
|
||||
|
||||
const roomsHelpNode = this.state.editing ? <ToolTipButton helpText={
|
||||
_t(
|
||||
'These rooms are displayed to community members on the community page. '+
|
||||
'Community members can join the rooms by clicking on them.',
|
||||
)
|
||||
} /> : <div />;
|
||||
|
||||
const addRoomRow = this.state.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_rooms_header_addRow"
|
||||
|
@ -699,14 +722,23 @@ export default React.createClass({
|
|||
{ _t('Add rooms to this community') }
|
||||
</div>
|
||||
</AccessibleButton>) : <div />;
|
||||
const roomDetailListClassName = classnames({
|
||||
"mx_fadable": true,
|
||||
"mx_fadable_faded": this.state.editing,
|
||||
});
|
||||
return <div className="mx_GroupView_rooms">
|
||||
<div className="mx_GroupView_rooms_header">
|
||||
<h3>{ _t('Rooms') }</h3>
|
||||
<h3>
|
||||
{ _t('Rooms') }
|
||||
{ roomsHelpNode }
|
||||
</h3>
|
||||
{ addRoomRow }
|
||||
</div>
|
||||
{ this.state.groupRoomsLoading ?
|
||||
<Spinner /> :
|
||||
<RoomDetailList rooms={this.state.groupRooms} />
|
||||
<RoomDetailList
|
||||
rooms={this.state.groupRooms}
|
||||
className={roomDetailListClassName} />
|
||||
}
|
||||
</div>;
|
||||
},
|
||||
|
@ -894,6 +926,18 @@ export default React.createClass({
|
|||
let description = null;
|
||||
if (summary.profile && summary.profile.long_description) {
|
||||
description = sanitizedHtmlNode(summary.profile.long_description);
|
||||
} else if (this.state.isUserPrivileged) {
|
||||
description = <div
|
||||
className="mx_GroupView_groupDesc_placeholder"
|
||||
onClick={this._onEditClick}
|
||||
>
|
||||
{ _tJsx(
|
||||
'Your community hasn\'t got a Long Description, a HTML page to show to community members.<br />' +
|
||||
'Click here to open settings and give it one!',
|
||||
[/<br \/>/],
|
||||
[(sub) => <br />])
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
const groupDescEditingClasses = classnames({
|
||||
"mx_GroupView_groupDesc": true,
|
||||
|
@ -905,6 +949,7 @@ export default React.createClass({
|
|||
<h3> { _t("Long Description (HTML)") } </h3>
|
||||
<textarea
|
||||
value={this.state.profileForm.long_description}
|
||||
placeholder={_t(LONG_DESC_PLACEHOLDER)}
|
||||
onChange={this._onLongDescChange}
|
||||
tabIndex="4"
|
||||
key="editLongDesc"
|
||||
|
|
|
@ -301,6 +301,7 @@ export default React.createClass({
|
|||
case PageTypes.GroupView:
|
||||
page_element = <GroupView
|
||||
groupId={this.props.currentGroupId}
|
||||
isNew={this.props.currentGroupIsNew}
|
||||
collapsedRhs={this.props.collapseRhs}
|
||||
/>;
|
||||
if (!this.props.collapseRhs) right_panel = <RightPanel groupId={this.props.currentGroupId} disabled={this.props.rightDisabled} />;
|
||||
|
|
|
@ -490,7 +490,10 @@ module.exports = React.createClass({
|
|||
case 'view_group':
|
||||
{
|
||||
const groupId = payload.group_id;
|
||||
this.setState({currentGroupId: groupId});
|
||||
this.setState({
|
||||
currentGroupId: groupId,
|
||||
currentGroupIsNew: payload.group_is_new,
|
||||
});
|
||||
this._setPage(PageTypes.GroupView);
|
||||
this.notifyNewScreen('group/' + groupId);
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ export default React.createClass({
|
|||
dis.dispatch({
|
||||
action: 'view_group',
|
||||
group_id: result.group_id,
|
||||
group_is_new: true,
|
||||
});
|
||||
this.props.onFinished(true);
|
||||
}).catch((e) => {
|
||||
|
|
|
@ -49,7 +49,8 @@ function UserUnknownDeviceList(props) {
|
|||
|
||||
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
|
||||
<DeviceListEntry key={deviceId} userId={userId}
|
||||
device={userDevices[deviceId]} />,
|
||||
device={userDevices[deviceId]}
|
||||
/>,
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -92,26 +93,60 @@ export default React.createClass({
|
|||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
|
||||
// map from userid -> deviceid -> deviceinfo
|
||||
devices: React.PropTypes.object.isRequired,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
|
||||
const roomMembers = this.props.room.getJoinedMembers().map((m) => {
|
||||
return m.userId;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
// map from userid -> deviceid -> deviceinfo
|
||||
devices: null,
|
||||
});
|
||||
MatrixClientPeg.get().downloadKeys(roomMembers, false).then((devices) => {
|
||||
if (this._unmounted) return;
|
||||
|
||||
const unknownDevices = {};
|
||||
// This is all devices in this room, so find the unknown ones.
|
||||
Object.keys(devices).forEach((userId) => {
|
||||
Object.keys(devices[userId]).map((deviceId) => {
|
||||
const device = devices[userId][deviceId];
|
||||
|
||||
if (device.isUnverified() && !device.isKnown()) {
|
||||
if (unknownDevices[userId] === undefined) {
|
||||
unknownDevices[userId] = {};
|
||||
}
|
||||
unknownDevices[userId][deviceId] = device;
|
||||
}
|
||||
|
||||
// Given we've now shown the user the unknown device, it is no longer
|
||||
// unknown to them. Therefore mark it as 'known'.
|
||||
Object.keys(this.props.devices).forEach((userId) => {
|
||||
Object.keys(this.props.devices[userId]).map((deviceId) => {
|
||||
if (!device.isKnown()) {
|
||||
MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// XXX: temporary logging to try to diagnose
|
||||
// https://github.com/vector-im/riot-web/issues/3148
|
||||
console.log('Opening UnknownDeviceDialog');
|
||||
this.setState({
|
||||
devices: unknownDevices,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._unmounted = true;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.devices === null) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const blacklistUnverified = client.getGlobalBlacklistUnverifiedDevices() ||
|
||||
this.props.room.getBlacklistUnverifiedDevices();
|
||||
|
@ -154,7 +189,7 @@ export default React.createClass({
|
|||
{ warning }
|
||||
{ _t("Unknown devices") }:
|
||||
|
||||
<UnknownDeviceList devices={this.props.devices} />
|
||||
<UnknownDeviceList devices={this.state.devices} />
|
||||
</GeminiScrollbar>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className="mx_Dialog_primary" autoFocus={true}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2017 New Vector Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ToolTipButton',
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hover: false,
|
||||
};
|
||||
},
|
||||
|
||||
onMouseOver: function() {
|
||||
this.setState({
|
||||
hover: true,
|
||||
});
|
||||
},
|
||||
|
||||
onMouseOut: function() {
|
||||
this.setState({
|
||||
hover: false,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||
const tip = this.state.hover ? <RoomTooltip
|
||||
className="mx_ToolTipButton_container"
|
||||
tooltipClassName="mx_ToolTipButton_helpText"
|
||||
label={this.props.helpText}
|
||||
/> : <div />;
|
||||
return (
|
||||
<div className="mx_ToolTipButton" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} >
|
||||
?
|
||||
{ tip }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -63,7 +63,7 @@ export default withMatrixClient(React.createClass({
|
|||
return (
|
||||
<EntityTile name={name} avatarJsx={av} onClick={this.onClick}
|
||||
suppressOnHover={true} presenceState="online"
|
||||
powerStatus={this.props.member.isAdmin ? EntityTile.POWER_STATUS_ADMIN : null}
|
||||
powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -19,6 +19,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg";
|
|||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import PinnedEventTile from "./PinnedEventTile";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import PinningUtils from "../../../utils/PinningUtils";
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'PinnedEventsPanel',
|
||||
|
@ -61,20 +62,39 @@ module.exports = React.createClass({
|
|||
|
||||
Promise.all(promises).then((contexts) => {
|
||||
// Filter out the messages before we try to render them
|
||||
const pinned = contexts.filter((context) => {
|
||||
if (!context) return false; // no context == not applicable for the room
|
||||
if (context.event.getType() !== "m.room.message") return false;
|
||||
if (context.event.isRedacted()) return false;
|
||||
return true;
|
||||
});
|
||||
const pinned = contexts.filter((context) => PinningUtils.isPinnable(context.event));
|
||||
|
||||
this.setState({ loading: false, pinned });
|
||||
});
|
||||
}
|
||||
|
||||
this._updateReadState();
|
||||
},
|
||||
|
||||
_updateReadState: function() {
|
||||
const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", "");
|
||||
if (!pinnedEvents) return; // nothing to read
|
||||
|
||||
let readStateEvents = [];
|
||||
const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins");
|
||||
if (readPinsEvent && readPinsEvent.getContent()) {
|
||||
readStateEvents = readPinsEvent.getContent().event_ids || [];
|
||||
}
|
||||
|
||||
if (!readStateEvents.includes(pinnedEvents.getId())) {
|
||||
readStateEvents.push(pinnedEvents.getId());
|
||||
|
||||
// Only keep the last 10 event IDs to avoid infinite growth
|
||||
readStateEvents = readStateEvents.reverse().splice(0, 10).reverse();
|
||||
|
||||
MatrixClientPeg.get().setRoomAccountData(this.props.room.roomId, "im.vector.room.read_pins", {
|
||||
event_ids: readStateEvents,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_getPinnedTiles: function() {
|
||||
if (this.state.pinned.length == 0) {
|
||||
if (this.state.pinned.length === 0) {
|
||||
return (<div>{ _t("No pinned messages.") }</div>);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import sanitizeHtml from 'sanitize-html';
|
|||
import { ContentRepo } from 'matrix-js-sdk';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function getDisplayAliasForRoom(room) {
|
||||
return room.canonicalAlias || (room.aliases ? room.aliases[0] : "");
|
||||
|
@ -117,6 +118,8 @@ export default React.createClass({
|
|||
worldReadable: PropTypes.bool,
|
||||
guestCanJoin: PropTypes.bool,
|
||||
})),
|
||||
|
||||
className: PropTypes.string,
|
||||
},
|
||||
|
||||
getRows: function() {
|
||||
|
@ -138,7 +141,7 @@ export default React.createClass({
|
|||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
return <div className="mx_RoomDetailList">
|
||||
return <div className={classNames("mx_RoomDetailList", this.props.className)}>
|
||||
{ rooms }
|
||||
</div>;
|
||||
},
|
||||
|
|
|
@ -65,6 +65,7 @@ module.exports = React.createClass({
|
|||
componentDidMount: function() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("RoomState.events", this._onRoomStateEvents);
|
||||
cli.on("Room.accountData", this._onRoomAccountData);
|
||||
|
||||
// When a room name occurs, RoomState.events is fired *before*
|
||||
// room.name is updated. So we have to listen to Room.name as well as
|
||||
|
@ -87,6 +88,7 @@ module.exports = React.createClass({
|
|||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.removeListener("RoomState.events", this._onRoomStateEvents);
|
||||
cli.removeListener("Room.accountData", this._onRoomAccountData);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -99,6 +101,13 @@ module.exports = React.createClass({
|
|||
this._rateLimitedUpdate();
|
||||
},
|
||||
|
||||
_onRoomAccountData: function(event, room) {
|
||||
if (!this.props.room || room.roomId !== this.props.room.roomId) return;
|
||||
if (event.getType() !== "im.vector.room.read_pins") return;
|
||||
|
||||
this._rateLimitedUpdate();
|
||||
},
|
||||
|
||||
_rateLimitedUpdate: new RateLimitedFunc(function() {
|
||||
/* eslint-disable babel/no-invalid-this */
|
||||
this.forceUpdate();
|
||||
|
@ -139,6 +148,32 @@ module.exports = React.createClass({
|
|||
dis.dispatch({ action: 'show_right_panel' });
|
||||
},
|
||||
|
||||
_hasUnreadPins: function() {
|
||||
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
|
||||
if (!currentPinEvent) return false;
|
||||
if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) {
|
||||
return false; // no pins == nothing to read
|
||||
}
|
||||
|
||||
const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins");
|
||||
if (readPinsEvent && readPinsEvent.getContent()) {
|
||||
const readStateEvents = readPinsEvent.getContent().event_ids || [];
|
||||
if (readStateEvents) {
|
||||
return !readStateEvents.includes(currentPinEvent.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// There's pins, and we haven't read any of them
|
||||
return true;
|
||||
},
|
||||
|
||||
_hasPins: function() {
|
||||
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
|
||||
if (!currentPinEvent) return false;
|
||||
|
||||
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* After editing the settings, get the new name for the room
|
||||
*
|
||||
|
@ -305,8 +340,17 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) {
|
||||
let pinsIndicator = null;
|
||||
if (this._hasUnreadPins()) {
|
||||
pinsIndicator = (<div className="mx_RoomHeader_pinsIndicator mx_RoomHeader_pinsIndicatorUnread" />);
|
||||
} else if (this._hasPins()) {
|
||||
pinsIndicator = (<div className="mx_RoomHeader_pinsIndicator" />);
|
||||
}
|
||||
|
||||
pinnedEventsButton =
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
|
||||
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_pinnedButton"
|
||||
onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
|
||||
{ pinsIndicator }
|
||||
<TintableSvg src="img/icons-pin.svg" width="16" height="16" />
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ export function groupMemberFromApiObject(apiObject) {
|
|||
userId: apiObject.user_id,
|
||||
displayname: apiObject.displayname,
|
||||
avatarUrl: apiObject.avatar_url,
|
||||
isAdmin: apiObject.is_admin,
|
||||
isPrivileged: apiObject.is_privileged,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -672,6 +672,7 @@
|
|||
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
|
||||
"You must join the room to see its files": "You must join the room to see its files",
|
||||
"There are no visible files in this room": "There are no visible files in this room",
|
||||
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n",
|
||||
"Add rooms to the community summary": "Add rooms to the community summary",
|
||||
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
|
||||
"Add to summary": "Add to summary",
|
||||
|
@ -694,6 +695,7 @@
|
|||
"Leave": "Leave",
|
||||
"Unable to leave room": "Unable to leave room",
|
||||
"Community Settings": "Community Settings",
|
||||
"These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.",
|
||||
"Add rooms to this community": "Add rooms to this community",
|
||||
"Featured Rooms:": "Featured Rooms:",
|
||||
"Featured Users:": "Featured Users:",
|
||||
|
@ -702,6 +704,7 @@
|
|||
"You are a member of this community": "You are a member of this community",
|
||||
"Community Member Settings": "Community Member Settings",
|
||||
"Publish this community on your profile": "Publish this community on your profile",
|
||||
"Your community hasn't got a Long Description, a HTML page to show to community members.<br />Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members.<br />Click here to open settings and give it one!",
|
||||
"Long Description (HTML)": "Long Description (HTML)",
|
||||
"Description": "Description",
|
||||
"Community %(groupId)s not found": "Community %(groupId)s not found",
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2017 Travis Ralston
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export default class PinningUtils {
|
||||
/**
|
||||
* Determines if the given event may be pinned.
|
||||
* @param {MatrixEvent} event The event to check.
|
||||
* @return {boolean} True if the event may be pinned, false otherwise.
|
||||
*/
|
||||
static isPinnable(event) {
|
||||
if (!event) return false;
|
||||
if (event.getType() !== "m.room.message") return false;
|
||||
if (event.isRedacted()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue