diff --git a/res/css/views/elements/_ReplyThread.scss b/res/css/views/elements/_ReplyThread.scss index af8ca956ba..44532ea6a7 100644 --- a/res/css/views/elements/_ReplyThread.scss +++ b/res/css/views/elements/_ReplyThread.scss @@ -16,19 +16,16 @@ limitations under the License. .mx_ReplyThread { margin-top: 0; -} - -.mx_ReplyThread_show { - cursor: pointer; -} - -blockquote.mx_ReplyThread { margin-left: 0; margin-right: 0; margin-bottom: 8px; padding-left: 10px; border-left: 4px solid $button-bg-color; + .mx_ReplyThread_show { + cursor: pointer; + } + &.mx_ReplyThread_color1 { border-left-color: $username-variant1-color; } diff --git a/res/css/views/rooms/_ReplyPreview.scss b/res/css/views/rooms/_ReplyPreview.scss index c1fe1d9a8b..60feb39d11 100644 --- a/res/css/views/rooms/_ReplyPreview.scss +++ b/res/css/views/rooms/_ReplyPreview.scss @@ -22,33 +22,34 @@ limitations under the License. max-height: 50vh; overflow: auto; box-shadow: 0px -16px 32px $composer-shadow-color; + + .mx_ReplyPreview_section { + border-bottom: 1px solid $primary-hairline-color; + + .mx_ReplyPreview_header { + margin: 8px; + color: $primary-fg-color; + font-weight: 400; + opacity: 0.4; + } + + .mx_ReplyPreview_tile { + margin: 0 8px; + } + + .mx_ReplyPreview_title { + float: left; + } + + .mx_ReplyPreview_cancel { + float: right; + cursor: pointer; + display: flex; + } + + .mx_ReplyPreview_clear { + clear: both; + } + } } -.mx_ReplyPreview_section { - border-bottom: 1px solid $primary-hairline-color; -} - -.mx_ReplyPreview_header { - margin: 8px; - color: $primary-fg-color; - font-weight: 400; - opacity: 0.4; -} - -.mx_ReplyPreview_tile { - margin: 0 8px; -} - -.mx_ReplyPreview_title { - float: left; -} - -.mx_ReplyPreview_cancel { - float: right; - cursor: pointer; - display: flex; -} - -.mx_ReplyPreview_clear { - clear: both; -} diff --git a/res/css/views/rooms/_ReplyTile.scss b/res/css/views/rooms/_ReplyTile.scss index c8f76ee995..f3e204e415 100644 --- a/res/css/views/rooms/_ReplyTile.scss +++ b/res/css/views/rooms/_ReplyTile.scss @@ -15,10 +15,9 @@ limitations under the License. */ .mx_ReplyTile { - padding-top: 2px; - padding-bottom: 2px; - font-size: $font-14px; position: relative; + padding: 2px 0; + font-size: $font-14px; line-height: $font-16px; &.mx_ReplyTile_audio .mx_MFileBody_info_icon::before { @@ -38,86 +37,83 @@ limitations under the License. display: none; } } -} -.mx_ReplyTile > a { - display: flex; - flex-direction: column; - text-decoration: none; - color: $primary-fg-color; -} - -.mx_ReplyTile .mx_RedactedBody { - padding: 4px 0 2px 20px; - - &::before { - height: 13px; - width: 13px; - top: 5px; - } -} - -// We do reply size limiting with CSS to avoid duplicating the TextualBody component. -.mx_ReplyTile .mx_EventTile_content { - $reply-lines: 2; - $line-height: $font-22px; - - pointer-events: none; - - text-overflow: ellipsis; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: $reply-lines; - line-height: $line-height; - - .mx_EventTile_body.mx_EventTile_bigEmoji { - line-height: $line-height !important; - // Override the big emoji override - font-size: $font-14px !important; + > a { + display: flex; + flex-direction: column; + text-decoration: none; + color: $primary-fg-color; } - // Hide line numbers - .mx_EventTile_lineNumbers { - display: none; + .mx_RedactedBody { + padding: 4px 0 2px 20px; + + &::before { + height: 13px; + width: 13px; + top: 5px; + } } - // Hack to cut content in
tags too
- .mx_EventTile_pre_container > pre {
- overflow: hidden;
+ // We do reply size limiting with CSS to avoid duplicating the TextualBody component.
+ .mx_EventTile_content {
+ $reply-lines: 2;
+ $line-height: $font-22px;
+
+ pointer-events: none;
+
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $reply-lines;
- padding: 4px;
+ line-height: $line-height;
+
+ .mx_EventTile_body.mx_EventTile_bigEmoji {
+ line-height: $line-height !important;
+ font-size: $font-14px !important; // Override the big emoji override
+ }
+
+ // Hide line numbers
+ .mx_EventTile_lineNumbers {
+ display: none;
+ }
+
+ // Hack to cut content in tags too
+ .mx_EventTile_pre_container > pre {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: $reply-lines;
+ padding: 4px;
+ }
+
+ .markdown-body blockquote,
+ .markdown-body dl,
+ .markdown-body ol,
+ .markdown-body p,
+ .markdown-body pre,
+ .markdown-body table,
+ .markdown-body ul {
+ margin-bottom: 4px;
+ }
}
- .markdown-body blockquote,
- .markdown-body dl,
- .markdown-body ol,
- .markdown-body p,
- .markdown-body pre,
- .markdown-body table,
- .markdown-body ul {
- margin-bottom: 4px;
+ &.mx_ReplyTile_info {
+ padding-top: 0;
+ }
+
+ .mx_SenderProfile {
+ font-size: $font-14px;
+ line-height: $font-17px;
+
+ display: inline-block; // anti-zalgo, with overflow hidden
+ padding: 0;
+ margin: 0;
+
+ // truncate long display names
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
}
-
-.mx_ReplyTile.mx_ReplyTile_info {
- padding-top: 0;
-}
-
-.mx_ReplyTile .mx_SenderProfile {
- color: $primary-fg-color;
- font-size: $font-14px;
- display: inline-block; /* anti-zalgo, with overflow hidden */
- overflow: hidden;
- cursor: pointer;
- padding-left: 0; /* left gutter */
- padding-bottom: 0;
- padding-top: 0;
- margin: 0;
- line-height: $font-17px;
- /* the next three lines, along with overflow hidden, truncate long display names */
- white-space: nowrap;
- text-overflow: ellipsis;
-}
diff --git a/src/ActiveRoomObserver.ts b/src/ActiveRoomObserver.ts
index 1126dc9496..0be49a24ea 100644
--- a/src/ActiveRoomObserver.ts
+++ b/src/ActiveRoomObserver.ts
@@ -15,6 +15,7 @@ limitations under the License.
*/
import RoomViewStore from './stores/RoomViewStore';
+import { EventSubscription } from 'fbemitter';
type Listener = (isActive: boolean) => void;
@@ -30,7 +31,7 @@ type Listener = (isActive: boolean) => void;
export class ActiveRoomObserver {
private listeners: {[key: string]: Listener[]} = {};
private _activeRoomId = RoomViewStore.getRoomId();
- private readonly roomStoreToken: string;
+ private readonly roomStoreToken: EventSubscription;
constructor() {
// TODO: We could self-destruct when the last listener goes away, or at least stop listening.
diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.tsx
similarity index 85%
rename from src/components/views/elements/ReplyThread.js
rename to src/components/views/elements/ReplyThread.tsx
index 89427515e2..0eb795e257 100644
--- a/src/components/views/elements/ReplyThread.js
+++ b/src/components/views/elements/ReplyThread.tsx
@@ -14,14 +14,14 @@ 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 { _t } from '../../../languageHandler';
-import PropTypes from 'prop-types';
import dis from '../../../dispatcher/dispatcher';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore";
-import { LayoutPropType } from "../../../settings/Layout";
+import { Layout } from "../../../settings/Layout";
import escapeHtml from "escape-html";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
@@ -32,51 +32,54 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import Spinner from './Spinner';
import ReplyTile from "../rooms/ReplyTile";
import Pill from './Pill';
+import { Room } from 'matrix-js-sdk/src/models/room';
+
+interface IProps {
+ // the latest event in this chain of replies
+ parentEv?: MatrixEvent;
+ // called when the ReplyThread contents has changed, including EventTiles thereof
+ onHeightChanged: () => void;
+ permalinkCreator: RoomPermalinkCreator;
+ // Specifies which layout to use.
+ layout?: Layout;
+ // Whether to always show a timestamp
+ alwaysShowTimestamps?: boolean;
+}
+
+interface IState {
+ // The loaded events to be rendered as linear-replies
+ events: MatrixEvent[];
+ // The latest loaded event which has not yet been shown
+ loadedEv: MatrixEvent;
+ // Whether the component is still loading more events
+ loading: boolean;
+ // Whether as error was encountered fetching a replied to event.
+ err: boolean;
+}
// This component does no cycle detection, simply because the only way to make such a cycle would be to
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
// be low as each event being loaded (after the first) is triggered by an explicit user action.
@replaceableComponent("views.elements.ReplyThread")
-export default class ReplyThread extends React.Component {
- static propTypes = {
- // the latest event in this chain of replies
- parentEv: PropTypes.instanceOf(MatrixEvent),
- // called when the ReplyThread contents has changed, including EventTiles thereof
- onHeightChanged: PropTypes.func.isRequired,
- permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
- // Specifies which layout to use.
- layout: LayoutPropType,
- // Whether to always show a timestamp
- alwaysShowTimestamps: PropTypes.bool,
- };
-
+export default class ReplyThread extends React.Component {
static contextType = MatrixClientContext;
+ private unmounted = false;
+ private room: Room;
constructor(props, context) {
super(props, context);
this.state = {
- // The loaded events to be rendered as linear-replies
events: [],
-
- // The latest loaded event which has not yet been shown
loadedEv: null,
- // Whether the component is still loading more events
loading: true,
-
- // Whether as error was encountered fetching a replied to event.
err: false,
};
- this.unmounted = false;
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
-
- this.onQuoteClick = this.onQuoteClick.bind(this);
- this.canCollapse = this.canCollapse.bind(this);
- this.collapse = this.collapse.bind(this);
}
- static getParentEventId(ev) {
+ public static getParentEventId(ev: MatrixEvent): string {
if (!ev || ev.isRedacted()) return;
// XXX: For newer relations (annotations, replacements, etc.), we now
@@ -92,7 +95,7 @@ export default class ReplyThread extends React.Component {
}
// Part of Replies fallback support
- static stripPlainReply(body) {
+ public static stripPlainReply(body: string): string {
// Removes lines beginning with `> ` until you reach one that doesn't.
const lines = body.split('\n');
while (lines.length && lines[0].startsWith('> ')) lines.shift();
@@ -102,7 +105,7 @@ export default class ReplyThread extends React.Component {
}
// Part of Replies fallback support
- static stripHTMLReply(html) {
+ public static stripHTMLReply(html: string): string {
// Sanitize the original HTML for inclusion in . We allow
// any HTML, since the original sender could use special tags that we
// don't recognize, but want to pass along to any recipients who do
@@ -124,7 +127,10 @@ export default class ReplyThread extends React.Component {
}
// Part of Replies fallback support
- static getNestedReplyText(ev, permalinkCreator) {
+ public static getNestedReplyText(
+ ev: MatrixEvent,
+ permalinkCreator: RoomPermalinkCreator,
+ ): { body: string, html: string } {
if (!ev) return null;
let { body, formatted_body: html } = ev.getContent();
@@ -200,7 +206,7 @@ export default class ReplyThread extends React.Component {
return { body, html };
}
- static makeReplyMixIn(ev) {
+ public static makeReplyMixIn(ev: MatrixEvent) {
if (!ev) return {};
return {
'm.relates_to': {
@@ -211,10 +217,15 @@ export default class ReplyThread extends React.Component {
};
}
- static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout, alwaysShowTimestamps) {
- if (!ReplyThread.getParentEventId(parentEv)) {
- return null;
- }
+ public static makeThread(
+ parentEv: MatrixEvent,
+ onHeightChanged: () => void,
+ permalinkCreator: RoomPermalinkCreator,
+ ref: React.RefObject,
+ layout: Layout,
+ alwaysShowTimestamps: boolean,
+ ): JSX.Element {
+ if (!ReplyThread.getParentEventId(parentEv)) return null;
return {
const { parentEv } = this.props;
// at time of making this component we checked that props.parentEv has a parentEventId
const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv));
@@ -256,7 +267,7 @@ export default class ReplyThread extends React.Component {
}
}
- async getNextEvent(ev) {
+ private async getNextEvent(ev: MatrixEvent): Promise {
try {
const inReplyToEventId = ReplyThread.getParentEventId(ev);
return await this.getEvent(inReplyToEventId);
@@ -265,7 +276,7 @@ export default class ReplyThread extends React.Component {
}
}
- async getEvent(eventId) {
+ private async getEvent(eventId: string): Promise {
if (!eventId) return null;
const event = this.room.findEventById(eventId);
if (event) return event;
@@ -282,15 +293,15 @@ export default class ReplyThread extends React.Component {
return this.room.findEventById(eventId);
}
- canCollapse() {
+ public canCollapse = (): boolean => {
return this.state.events.length > 1;
- }
+ };
- collapse() {
+ public collapse = (): void => {
this.initialize();
- }
+ };
- async onQuoteClick() {
+ private onQuoteClick = async (): Promise => {
const events = [this.state.loadedEv, ...this.state.events];
let loadedEv = null;
@@ -304,9 +315,9 @@ export default class ReplyThread extends React.Component {
});
dis.fire(Action.FocusSendMessageComposer);
- }
+ };
- getReplyThreadColorClass(ev) {
+ private getReplyThreadColorClass(ev: MatrixEvent): string {
return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyThread");
}
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index e0a924f1e7..1bdcccd77f 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -320,7 +320,7 @@ export default class EventTile extends React.Component {
private suppressReadReceiptAnimation: boolean;
private isListeningForReceipts: boolean;
private tile = React.createRef();
- private replyThread = React.createRef();
+ private replyThread = React.createRef();
public readonly ref = createRef();
diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.tsx
similarity index 81%
rename from src/components/views/rooms/ReplyPreview.js
rename to src/components/views/rooms/ReplyPreview.tsx
index c7d19e58db..41b3d2460c 100644
--- a/src/components/views/rooms/ReplyPreview.js
+++ b/src/components/views/rooms/ReplyPreview.tsx
@@ -18,10 +18,11 @@ import React from 'react';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import RoomViewStore from '../../../stores/RoomViewStore';
-import PropTypes from "prop-types";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import ReplyTile from './ReplyTile';
+import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
+import { EventSubscription } from 'fbemitter';
function cancelQuoting() {
dis.dispatch({
@@ -30,41 +31,46 @@ function cancelQuoting() {
});
}
+interface IProps {
+ permalinkCreator: RoomPermalinkCreator;
+}
+
+interface IState {
+ event: MatrixEvent;
+}
+
@replaceableComponent("views.rooms.ReplyPreview")
-export default class ReplyPreview extends React.Component {
- static propTypes = {
- permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
- };
+export default class ReplyPreview extends React.Component {
+ private unmounted = false;
+ private readonly roomStoreToken: EventSubscription;
constructor(props) {
super(props);
- this.unmounted = false;
this.state = {
event: RoomViewStore.getQuotingEvent(),
};
- this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
- this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
+ this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
}
componentWillUnmount() {
this.unmounted = true;
// Remove RoomStore listener
- if (this._roomStoreToken) {
- this._roomStoreToken.remove();
+ if (this.roomStoreToken) {
+ this.roomStoreToken.remove();
}
}
- _onRoomViewStoreUpdate() {
+ private onRoomViewStoreUpdate = (): void => {
if (this.unmounted) return;
const event = RoomViewStore.getQuotingEvent();
if (this.state.event !== event) {
this.setState({ event });
}
- }
+ };
render() {
if (!this.state.event) return null;
diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx
index ddcb9057ec..895d9773e4 100644
--- a/src/components/views/voip/CallPreview.tsx
+++ b/src/components/views/voip/CallPreview.tsx
@@ -30,6 +30,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import UIStore from '../../../stores/UIStore';
import { lerp } from '../../../utils/AnimationUtils';
import { MarkedExecution } from '../../../utils/MarkedExecution';
+import { EventSubscription } from 'fbemitter';
const PIP_VIEW_WIDTH = 336;
const PIP_VIEW_HEIGHT = 232;
@@ -108,7 +109,7 @@ function getPrimarySecondaryCalls(calls: MatrixCall[]): [MatrixCall, MatrixCall[
*/
@replaceableComponent("views.voip.CallPreview")
export default class CallPreview extends React.Component {
- private roomStoreToken: any;
+ private roomStoreToken: EventSubscription;
private dispatcherRef: string;
private settingsWatcherRef: string;
private callViewWrapper = createRef();
@@ -240,7 +241,7 @@ export default class CallPreview extends React.Component {
this.scheduledUpdate.mark();
};
- private onRoomViewStoreUpdate = (payload) => {
+ private onRoomViewStoreUpdate = () => {
if (RoomViewStore.getRoomId() === this.state.roomId) return;
const roomId = RoomViewStore.getRoomId();
diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index 10f42f3166..1a85ff59b1 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -429,7 +429,7 @@ class RoomViewStore extends Store {
}
}
-let singletonRoomViewStore = null;
+let singletonRoomViewStore: RoomViewStore = null;
if (!singletonRoomViewStore) {
singletonRoomViewStore = new RoomViewStore();
}