Merge remote-tracking branch 'upstream/develop' into compact-reply-rendering

This commit is contained in:
Tulir Asokan 2020-09-19 19:14:37 +03:00
commit 987cdf1bf0
201 changed files with 6532 additions and 2503 deletions

View file

@ -15,11 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import classNames from 'classnames';
import {Resizable} from "re-resizable";
import AppTile from '../elements/AppTile';
import Modal from '../../../Modal';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import * as ScalarMessaging from '../../../ScalarMessaging';
@ -29,14 +30,15 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import AccessibleButton from '../elements/AccessibleButton';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import WidgetStore from "../../../stores/WidgetStore";
export default class AppsDrawer extends React.Component {
static propTypes = {
userId: PropTypes.string.isRequired,
room: PropTypes.object.isRequired,
resizeNotifier: PropTypes.instanceOf(ResizeNotifier).isRequired,
showApps: PropTypes.bool, // Should apps be rendered
hide: PropTypes.bool, // If rendered, should apps drawer be visible
};
@ -56,17 +58,13 @@ export default class AppsDrawer extends React.Component {
componentDidMount() {
ScalarMessaging.startListening();
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('update', this._updateApps);
WidgetStore.instance.on(this.props.room.roomId, this._updateApps);
this.dispatcherRef = dis.register(this.onAction);
}
componentWillUnmount() {
ScalarMessaging.stopListening();
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
WidgetEchoStore.removeListener('update', this._updateApps);
WidgetStore.instance.off(this.props.room.roomId, this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
}
@ -95,28 +93,11 @@ export default class AppsDrawer extends React.Component {
}
};
onRoomStateEvents = (ev, state) => {
if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
return;
}
this._updateApps();
};
_getApps() {
const widgets = WidgetEchoStore.getEchoedRoomWidgets(
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
);
return widgets.map((ev) => {
return WidgetUtils.makeAppConfig(
ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
);
});
}
_getApps = () => WidgetStore.instance.getApps(this.props.room, true);
_updateApps = () => {
const apps = this._getApps();
this.setState({
apps: apps,
apps: this._getApps(),
});
};
@ -139,18 +120,6 @@ export default class AppsDrawer extends React.Component {
onClickAddWidget = (e) => {
e.preventDefault();
// Display a warning dialog if the max number of widgets have already been added to the room
const apps = this._getApps();
if (apps && apps.length >= MAX_WIDGETS) {
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`;
console.error(errorMsg);
Modal.createDialog(ErrorDialog, {
title: _t('Cannot add any more widgets'),
description: _t('The maximum permitted number of widgets have already been added to this room.'),
});
return;
}
this._launchManageIntegrations();
};
@ -161,19 +130,19 @@ export default class AppsDrawer extends React.Component {
return (<AppTile
key={app.id}
app={app}
fullWidth={arr.length<2 ? true : false}
fullWidth={arr.length < 2}
room={this.props.room}
userId={this.props.userId}
show={this.props.showApps}
creatorUserId={app.creatorUserId}
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad}
whitelistCapabilities={capWhitelist}
/>);
});
if (apps.length == 0) {
return <div></div>;
if (apps.length === 0) {
return <div />;
}
let addWidget;
@ -202,14 +171,68 @@ export default class AppsDrawer extends React.Component {
spinner = <Loader />;
}
const classes = classNames({
"mx_AppsDrawer": true,
"mx_AppsDrawer_hidden": this.props.hide,
"mx_AppsDrawer_fullWidth": apps.length < 2,
"mx_AppsDrawer_minimised": !this.props.showApps,
});
return (
<div className={'mx_AppsDrawer' + (this.props.hide ? ' mx_AppsDrawer_hidden' : '')}>
<div id='apps' className='mx_AppsContainer'>
<div className={classes}>
<PersistentVResizer
id={"apps-drawer_" + this.props.room.roomId}
minHeight={100}
maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined}
handleClass="mx_AppsContainer_resizerHandle"
className="mx_AppsContainer"
resizeNotifier={this.props.resizeNotifier}
>
{ apps }
{ spinner }
</div>
</PersistentVResizer>
{ this._canUserModify() && addWidget }
</div>
);
}
}
const PersistentVResizer = ({
id,
minHeight,
maxHeight,
className,
handleWrapperClass,
handleClass,
resizeNotifier,
children,
}) => {
const [height, setHeight] = useLocalStorageState("pvr_" + id, 280); // old fixed height was 273px
const [resizing, setResizing] = useState(false);
return <Resizable
size={{height: Math.min(height, maxHeight)}}
minHeight={minHeight}
maxHeight={maxHeight}
onResizeStart={() => {
if (!resizing) setResizing(true);
resizeNotifier.startResizing();
}}
onResize={() => {
resizeNotifier.notifyTimelineHeightChanged();
}}
onResizeStop={(e, dir, ref, d) => {
setHeight(height + d.height);
if (resizing) setResizing(false);
resizeNotifier.stopResizing();
}}
handleWrapperClass={handleWrapperClass}
handleClasses={{bottom: handleClass}}
className={classNames(className, {
mx_AppsDrawer_resizing: resizing,
})}
enable={{bottom: true}}
>
{ children }
</Resizable>;
};

View file

@ -28,6 +28,7 @@ import RateLimitedFunc from '../../../ratelimitedfunc';
import SettingsStore from "../../../settings/SettingsStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import CallView from "../voip/CallView";
import {UIFeature} from "../../../settings/UIFeature";
export default class AuxPanel extends React.Component {
@ -198,17 +199,21 @@ export default class AuxPanel extends React.Component {
/>
);
const appsDrawer = <AppsDrawer
room={this.props.room}
userId={this.props.userId}
maxHeight={this.props.maxHeight}
showApps={this.props.showApps}
hide={this.props.hideAppsDrawer}
/>;
let appsDrawer;
if (SettingsStore.getValue(UIFeature.Widgets)) {
appsDrawer = <AppsDrawer
room={this.props.room}
userId={this.props.userId}
maxHeight={this.props.maxHeight}
showApps={this.props.showApps}
hide={this.props.hideAppsDrawer}
resizeNotifier={this.props.resizeNotifier}
/>;
}
let stateViews = null;
if (this.state.counters && SettingsStore.getValue("feature_state_counters")) {
let counters = [];
const counters = [];
this.state.counters.forEach((counter, idx) => {
const title = counter.title;
@ -217,7 +222,7 @@ export default class AuxPanel extends React.Component {
const severity = counter.severity;
const stateKey = counter.stateKey;
let span = <span>{ title }: { value }</span>
let span = <span>{ title }: { value }</span>;
if (link) {
span = (

View file

@ -207,7 +207,8 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
// If the user is entering a command, only consider them typing if it is one which sends a message into the room
if (isTyping && this.props.model.parts[0].type === "command") {
const {cmd} = parseCommandString(this.props.model.parts[0].text);
if (!CommandMap.has(cmd) || CommandMap.get(cmd).category !== CommandCategories.messages) {
const command = CommandMap.get(cmd);
if (!command || !command.isEnabled() || command.category !== CommandCategories.messages) {
isTyping = false;
}
}

View file

@ -34,6 +34,7 @@ import * as ObjectUtils from "../../../ObjectUtils";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {E2E_STATE} from "./E2EIcon";
import {toRem} from "../../../utils/units";
import RoomAvatar from "../avatars/RoomAvatar";
const eventTileTypes = {
'm.room.message': 'messages.MessageEvent',
@ -147,6 +148,10 @@ export default class EventTile extends React.Component {
*/
last: PropTypes.bool,
// true if the event is the last event in a section (adds a css class for
// targeting)
lastInSection: PropTypes.bool,
/* true if this is search context (which has the effect of greying out
* the text
*/
@ -206,6 +211,9 @@ export default class EventTile extends React.Component {
// whether to use the irc layout
useIRCLayout: PropTypes.bool,
// whether or not to show flair at all
enableFlair: PropTypes.bool,
};
static defaultProps = {
@ -670,6 +678,7 @@ export default class EventTile extends React.Component {
mx_EventTile_selected: this.props.isSelectedEvent,
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
mx_EventTile_last: this.props.last,
mx_EventTile_lastInSection: this.props.lastInSection,
mx_EventTile_contextual: this.props.contextual,
mx_EventTile_actionBarFocused: this.state.actionBarFocused,
mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2E_STATE.VERIFIED,
@ -736,10 +745,10 @@ export default class EventTile extends React.Component {
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
sender = <SenderProfile onClick={this.onSenderProfileClick}
mxEvent={this.props.mxEvent}
enableFlair={!text}
enableFlair={this.props.enableFlair && !text}
text={text} />;
} else {
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={this.props.enableFlair} />;
}
}
@ -818,6 +827,7 @@ export default class EventTile extends React.Component {
return (
<div className={classes} aria-live={ariaLive} aria-atomic="true">
<div className="mx_EventTile_roomName">
<RoomAvatar room={room} width={28} height={28} />
<a href={permalink} onClick={this.onPermalinkClicked}>
{ room ? room.name : '' }
</a>
@ -1027,11 +1037,7 @@ class E2ePadlock extends React.Component {
tooltip = <Tooltip className="mx_EventTile_e2eIcon_tooltip" label={this.props.title} dir="auto" />;
}
let classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
if (!SettingsStore.getValue("alwaysShowEncryptionIcons")) {
classes += ' mx_EventTile_e2eIcon_hidden';
}
const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
return (
<div
className={classes}

View file

@ -20,13 +20,14 @@ import React from 'react';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher/dispatcher';
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {isValid3pidInvite} from "../../../RoomInvite";
import rate_limited_func from "../../../ratelimitedfunc";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from "../../../index";
import CallHandler from "../../../CallHandler";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import BaseCard from "../right_panel/BaseCard";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
@ -438,7 +439,13 @@ export default class MemberList extends React.Component {
render() {
if (this.state.loading) {
const Spinner = sdk.getComponent("elements.Spinner");
return <div className="mx_MemberList"><Spinner /></div>;
return <BaseCard
className="mx_MemberList"
onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
>
<Spinner />
</BaseCard>;
}
const SearchBox = sdk.getComponent('structures.SearchBox');
@ -485,25 +492,29 @@ export default class MemberList extends React.Component {
/>;
}
return (
<div className="mx_MemberList" role="tabpanel">
{ inviteButton }
<AutoHideScrollbar>
<div className="mx_MemberList_wrapper">
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}
createOverflowElement={this._createOverflowTileJoined}
getChildren={this._getChildrenJoined}
getChildCount={this._getChildCountJoined} />
{ invitedHeader }
{ invitedSection }
</div>
</AutoHideScrollbar>
<SearchBox className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
placeholder={ _t('Filter room members') }
onSearch={ this.onSearchQueryChanged } />
</div>
const footer = (
<SearchBox
className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
placeholder={ _t('Filter room members') }
onSearch={ this.onSearchQueryChanged } />
);
return <BaseCard
className="mx_MemberList"
header={inviteButton}
footer={footer}
onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
>
<div className="mx_MemberList_wrapper">
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}
createOverflowElement={this._createOverflowTileJoined}
getChildren={this._getChildrenJoined}
getChildCount={this._getChildCountJoined} />
{ invitedHeader }
{ invitedSection }
</div>
</BaseCard>;
}
onInviteButtonClick = () => {

View file

@ -31,6 +31,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ReplyPreview from "./ReplyPreview";
import {UIFeature} from "../../../settings/UIFeature";
function ComposerAvatar(props) {
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
@ -384,9 +385,12 @@ export default class MessageComposer extends React.Component {
permalinkCreator={this.props.permalinkCreator} />,
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />,
);
if (SettingsStore.getValue(UIFeature.Widgets)) {
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
}
if (this.state.showCallButtons) {
if (callInProgress) {
controls.push(

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from "react";
import classNames from "classnames";
import { formatMinimalBadgeCount } from "../../../utils/FormattingUtils";
import { formatCount } from "../../../utils/FormattingUtils";
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
import { XOR } from "../../../@types/common";
@ -109,7 +109,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
if (!notification.hasUnreadCount) return null; // Can't render a badge
}
let symbol = notification.symbol || formatMinimalBadgeCount(notification.count);
let symbol = notification.symbol || formatCount(notification.count);
if (isEmptyBadge) symbol = "";
const classes = classNames({

View file

@ -18,14 +18,11 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import Modal from "../../../Modal";
import RateLimitedFunc from '../../../ratelimitedfunc';
import { linkifyElement } from '../../../HtmlUtils';
import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader';
import SettingsStore from "../../../settings/SettingsStore";
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
@ -114,13 +111,6 @@ export default class RoomHeader extends React.Component {
this.forceUpdate();
};
onShareRoomClick = (ev) => {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
target: this.props.room,
});
};
_hasUnreadPins() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false;
@ -150,7 +140,6 @@ export default class RoomHeader extends React.Component {
render() {
let searchStatus = null;
let cancelButton = null;
let settingsButton = null;
let pinnedEventsButton = null;
if (this.props.onCancelClick) {
@ -214,14 +203,6 @@ export default class RoomHeader extends React.Component {
/>;
}
if (this.props.onSettingsClick) {
settingsButton =
<AccessibleTooltipButton
className="mx_RoomHeader_button mx_RoomHeader_settingsButton"
onClick={this.props.onSettingsClick}
title={_t("Settings")} />;
}
if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) {
let pinsIndicator = null;
if (this._hasUnreadPins()) {
@ -258,26 +239,9 @@ export default class RoomHeader extends React.Component {
title={_t("Search")} />;
}
let shareRoomButton;
if (this.props.inRoom) {
shareRoomButton =
<AccessibleTooltipButton
className="mx_RoomHeader_button mx_RoomHeader_shareButton"
onClick={this.onShareRoomClick}
title={_t('Share room')} />;
}
let manageIntegsButton;
if (this.props.room && this.props.room.roomId && this.props.inRoom) {
manageIntegsButton = <ManageIntegsButton room={this.props.room} />;
}
const rightRow =
<div className="mx_RoomHeader_buttons">
{ settingsButton }
{ pinnedEventsButton }
{ shareRoomButton }
{ manageIntegsButton }
{ forgetButton }
{ searchButton }
</div>;

View file

@ -1,170 +0,0 @@
/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import PropTypes from "prop-types";
import * as sdk from "../../../index";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class RoomRecoveryReminder extends React.PureComponent {
static propTypes = {
// called if the user sets the option to suppress this reminder in the future
onDontAskAgainSet: PropTypes.func,
}
static defaultProps = {
onDontAskAgainSet: function() {},
}
constructor(props) {
super(props);
this.state = {
loading: true,
error: null,
backupInfo: null,
notNowClicked: false,
};
}
componentDidMount() {
this._loadBackupStatus();
}
async _loadBackupStatus() {
try {
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
this.setState({
loading: false,
backupInfo,
});
} catch (e) {
console.log("Unable to fetch key backup status", e);
this.setState({
loading: false,
error: e,
});
}
}
showSetupDialog = () => {
if (this.state.backupInfo) {
// A key backup exists for this account, but the creating device is not
// verified, so restore the backup which will give us the keys from it and
// allow us to trust it (ie. upload keys to it)
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
Modal.createTrackedDialog(
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
/* priority = */ false, /* static = */ true,
);
} else {
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
null, null, /* priority = */ false, /* static = */ true,
);
}
}
onOnNotNowClick = () => {
this.setState({notNowClicked: true});
}
onDontAskAgainClick = () => {
// When you choose "Don't ask again" from the room reminder, we show a
// dialog to confirm the choice.
Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder",
import("../../../async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog"),
{
onDontAskAgain: async () => {
await SettingsStore.setValue(
"showRoomRecoveryReminder",
null,
SettingLevel.ACCOUNT,
false,
);
this.props.onDontAskAgainSet();
},
onSetup: () => {
this.showSetupDialog();
},
},
);
}
onSetupClick = () => {
this.showSetupDialog();
}
render() {
// If there was an error loading just don't display the banner: we'll try again
// next time the user switchs to the room.
if (this.state.error || this.state.loading || this.state.notNowClicked) {
return null;
}
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
let setupCaption;
if (this.state.backupInfo) {
setupCaption = _t("Connect this session to Key Backup");
} else {
setupCaption = _t("Start using Key Backup");
}
return (
<div className="mx_RoomRecoveryReminder">
<div className="mx_RoomRecoveryReminder_header">{_t(
"Never lose encrypted messages",
)}</div>
<div className="mx_RoomRecoveryReminder_body">
<p>{_t(
"Messages in this room are secured with end-to-end " +
"encryption. Only you and the recipient(s) have the " +
"keys to read these messages.",
)}</p>
<p>{_t(
"Securely back up your keys to avoid losing them. " +
"<a>Learn more.</a>", {},
{
// TODO: We don't have this link yet: this will prevent the translators
// having to re-translate the string when we do.
a: sub => '',
},
)}</p>
</div>
<div className="mx_RoomRecoveryReminder_buttons">
<AccessibleButton kind="primary"
onClick={this.onSetupClick}>
{setupCaption}
</AccessibleButton>
<AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
onClick={this.onOnNotNowClick}>
{ _t("Not now") }
</AccessibleButton>
<AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
onClick={this.onDontAskAgainClick}>
{ _t("Don't ask me again") }
</AccessibleButton>
</div>
</div>
);
}
}

View file

@ -19,6 +19,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {haveTileForEvent} from "./EventTile";
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
export default class SearchResultTile extends React.Component {
static propTypes = {
@ -45,22 +47,31 @@ export default class SearchResultTile extends React.Component {
const ret = [<DateSeparator key={ts1 + "-search"} ts={ts1} />];
const timeline = result.context.getTimeline();
for (var j = 0; j < timeline.length; j++) {
for (let j = 0; j < timeline.length; j++) {
const ev = timeline[j];
var highlights;
let highlights;
const contextual = (j != result.context.getOurEventIndex());
if (!contextual) {
highlights = this.props.searchHighlights;
}
if (haveTileForEvent(ev)) {
ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights}
permalinkCreator={this.props.permalinkCreator}
highlightLink={this.props.resultLink}
onHeightChanged={this.props.onHeightChanged} />);
ret.push((
<EventTile
key={`${eventId}+${j}`}
mxEvent={ev}
contextual={contextual}
highlights={highlights}
permalinkCreator={this.props.permalinkCreator}
highlightLink={this.props.resultLink}
onHeightChanged={this.props.onHeightChanged}
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
/>
));
}
}
return (
<li data-scroll-tokens={eventId+"+"+j}>
<li data-scroll-tokens={eventId}>
{ ret }
</li>);
}