{ _t(info.feedbackSubheading) }
@@ -87,6 +87,7 @@ const BetaFeedbackDialog: React.FC
= ({featureId, onFinished}) => {
onChange={(ev) => {
setComment(ev.target.value);
}}
+ autoFocus={true}
/>
= ({ matrixClient: cli, space, onFin
diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.tsx
similarity index 64%
rename from src/components/views/messages/ReactionsRow.js
rename to src/components/views/messages/ReactionsRow.tsx
index d5c8ea2ac9..e1fcbe5364 100644
--- a/src/components/views/messages/ReactionsRow.js
+++ b/src/components/views/messages/ReactionsRow.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 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.
@@ -14,29 +14,68 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
+import React from "react";
+import classNames from "classnames";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { Relations } from "matrix-js-sdk/src/models/relations";
-import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { isContentActionable } from '../../../utils/EventUtils';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
+import { aboveLeftOf, ContextMenu, useContextMenu } from "../../structures/ContextMenu";
+import ReactionPicker from "../emojipicker/ReactionPicker";
+import ReactionsRowButton from "./ReactionsRowButton";
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
// The maximum number of reactions to initially show on a message.
const MAX_ITEMS_WHEN_LIMITED = 8;
-@replaceableComponent("views.messages.ReactionsRow")
-export default class ReactionsRow extends React.PureComponent {
- static propTypes = {
- // The event we're displaying reactions for
- mxEvent: PropTypes.object.isRequired,
- // The Relations model from the JS SDK for reactions to `mxEvent`
- reactions: PropTypes.object,
+const ReactButton = ({ mxEvent, reactions }: IProps) => {
+ const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
+
+ let contextMenu;
+ if (menuDisplayed) {
+ const buttonRect = button.current.getBoundingClientRect();
+ contextMenu =
+
+ ;
}
- constructor(props) {
- super(props);
+ return
+
+
+ { contextMenu }
+ ;
+};
+
+interface IProps {
+ // The event we're displaying reactions for
+ mxEvent: MatrixEvent;
+ // The Relations model from the JS SDK for reactions to `mxEvent`
+ reactions?: Relations;
+}
+
+interface IState {
+ myReactions: MatrixEvent[];
+ showAll: boolean;
+}
+
+@replaceableComponent("views.messages.ReactionsRow")
+export default class ReactionsRow extends React.PureComponent {
+ static contextType = MatrixClientContext;
+
+ constructor(props, context) {
+ super(props, context);
if (props.reactions) {
props.reactions.on("Relations.add", this.onReactionsChange);
@@ -92,7 +131,7 @@ export default class ReactionsRow extends React.PureComponent {
if (!reactions) {
return null;
}
- const userId = MatrixClientPeg.get().getUserId();
+ const userId = this.context.getUserId();
const myReactions = reactions.getAnnotationsBySender()[userId];
if (!myReactions) {
return null;
@@ -114,7 +153,6 @@ export default class ReactionsRow extends React.PureComponent {
return null;
}
- const ReactionsRowButton = sdk.getComponent('messages.ReactionsRowButton');
let items = reactions.getSortedAnnotationsByKey().map(([content, events]) => {
const count = events.size;
if (!count) {
@@ -151,13 +189,21 @@ export default class ReactionsRow extends React.PureComponent {
;
}
+ const cli = this.context;
+
+ let addReactionButton;
+ if (cli.getRoom(mxEvent.getRoomId()).currentState.maySendEvent(EventType.Reaction, cli.getUserId())) {
+ addReactionButton = ;
+ }
+
return
- {items}
- {showAllButton}
+ { items }
+ { showAllButton }
+ { addReactionButton }
;
}
}
diff --git a/src/components/views/messages/ReactionsRowButton.js b/src/components/views/messages/ReactionsRowButton.tsx
similarity index 73%
rename from src/components/views/messages/ReactionsRowButton.js
rename to src/components/views/messages/ReactionsRowButton.tsx
index b37a949e57..d163f1ad30 100644
--- a/src/components/views/messages/ReactionsRowButton.js
+++ b/src/components/views/messages/ReactionsRowButton.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 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.
@@ -14,49 +14,54 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
+import React from "react";
+import classNames from "classnames";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
-import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import dis from "../../../dispatcher/dispatcher";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
+import AccessibleButton from "../elements/AccessibleButton";
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
+
+interface IProps {
+ // The event we're displaying reactions for
+ mxEvent: MatrixEvent;
+ // The reaction content / key / emoji
+ content: string;
+ // The count of votes for this key
+ count: number;
+ // A Set of Matrix reaction events for this key
+ reactionEvents: Set;
+ // A possible Matrix event if the current user has voted for this type
+ myReactionEvent?: MatrixEvent;
+}
+
+interface IState {
+ tooltipRendered: boolean;
+ tooltipVisible: boolean;
+}
@replaceableComponent("views.messages.ReactionsRowButton")
-export default class ReactionsRowButton extends React.PureComponent {
- static propTypes = {
- // The event we're displaying reactions for
- mxEvent: PropTypes.object.isRequired,
- // The reaction content / key / emoji
- content: PropTypes.string.isRequired,
- // The count of votes for this key
- count: PropTypes.number.isRequired,
- // A Set of Martix reaction events for this key
- reactionEvents: PropTypes.object.isRequired,
- // A possible Matrix event if the current user has voted for this type
- myReactionEvent: PropTypes.object,
- }
+export default class ReactionsRowButton extends React.PureComponent {
+ static contextType = MatrixClientContext;
- constructor(props) {
- super(props);
+ state = {
+ tooltipRendered: false,
+ tooltipVisible: false,
+ };
- this.state = {
- tooltipVisible: false,
- };
- }
-
- onClick = (ev) => {
+ onClick = () => {
const { mxEvent, myReactionEvent, content } = this.props;
if (myReactionEvent) {
- MatrixClientPeg.get().redactEvent(
+ this.context.redactEvent(
mxEvent.getRoomId(),
myReactionEvent.getId(),
);
} else {
- MatrixClientPeg.get().sendEvent(mxEvent.getRoomId(), "m.reaction", {
+ this.context.sendEvent(mxEvent.getRoomId(), "m.reaction", {
"m.relates_to": {
"rel_type": "m.annotation",
"event_id": mxEvent.getId(),
@@ -83,8 +88,6 @@ export default class ReactionsRowButton extends React.PureComponent {
}
render() {
- const ReactionsRowButtonTooltip =
- sdk.getComponent('messages.ReactionsRowButtonTooltip');
const { mxEvent, content, count, reactionEvents, myReactionEvent } = this.props;
const classes = classNames({
@@ -102,7 +105,7 @@ export default class ReactionsRowButton extends React.PureComponent {
/>;
}
- const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
+ const room = this.context.getRoom(mxEvent.getRoomId());
let label;
if (room) {
const senders = [];
@@ -130,7 +133,6 @@ export default class ReactionsRowButton extends React.PureComponent {
);
}
const isPeeking = room.getMyMembership() !== "join";
- const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return ;
+ visible: boolean;
+}
@replaceableComponent("views.messages.ReactionsRowButtonTooltip")
-export default class ReactionsRowButtonTooltip extends React.PureComponent {
- static propTypes = {
- // The event we're displaying reactions for
- mxEvent: PropTypes.object.isRequired,
- // The reaction content / key / emoji
- content: PropTypes.string.isRequired,
- // A Set of Martix reaction events for this key
- reactionEvents: PropTypes.object.isRequired,
- visible: PropTypes.bool.isRequired,
- }
+export default class ReactionsRowButtonTooltip extends React.PureComponent {
+ static contextType = MatrixClientContext;
render() {
- const Tooltip = sdk.getComponent('elements.Tooltip');
const { content, reactionEvents, mxEvent, visible } = this.props;
- const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
+ const room = this.context.getRoom(mxEvent.getRoomId());
let tooltipLabel;
if (room) {
const senders = [];
diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx
index b461e2230e..0ebf511018 100644
--- a/src/components/views/spaces/SpaceCreateMenu.tsx
+++ b/src/components/views/spaces/SpaceCreateMenu.tsx
@@ -178,7 +178,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index b977c2073a..0b8f3b38e6 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -784,7 +784,7 @@
"%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.",
"You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "You can leave the beta any time from settings or tapping on a beta badge, like the one above.",
"Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.",
- "You’re using an early version of Spaces, your feedback will really help inform the next versions.": "You’re using an early version of Spaces, your feedback will really help inform the next versions.",
+ "Your feedback will help make spaces better. The more detail you can go into, the better.": "Your feedback will help make spaces better. The more detail you can go into, the better.",
"Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode",
"Send and receive voice messages": "Send and receive voice messages",
"Render LaTeX maths in messages": "Render LaTeX maths in messages",
@@ -1856,6 +1856,7 @@
"You sent a verification request": "You sent a verification request",
"Error decrypting video": "Error decrypting video",
"Error processing voice message": "Error processing voice message",
+ "Add reaction": "Add reaction",
"Show all": "Show all",
"Reactions": "Reactions",
" reacted with %(content)s": " reacted with %(content)s",
@@ -2059,6 +2060,7 @@
"Beta feedback": "Beta feedback",
"Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.",
"Done": "Done",
+ "%(featureName)s beta feedback": "%(featureName)s beta feedback",
"Your platform and username will be noted to help us use your feedback as much as we can.": "Your platform and username will be noted to help us use your feedback as much as we can.",
"To leave the beta, visit your settings.": "To leave the beta, visit your settings.",
"Feedback": "Feedback",
diff --git a/src/linkify-matrix.js b/src/linkify-matrix.js
index 84a131f23a..feda257d8b 100644
--- a/src/linkify-matrix.js
+++ b/src/linkify-matrix.js
@@ -254,11 +254,15 @@ matrixLinkify.options = {
target: function(href, type) {
if (type === 'url') {
- const transformed = tryTransformPermalinkToLocalHref(href);
- if (transformed !== href || decodeURIComponent(href).match(matrixLinkify.ELEMENT_URL_PATTERN)) {
- return null;
- } else {
- return '_blank';
+ try {
+ const transformed = tryTransformPermalinkToLocalHref(href);
+ if (transformed !== href || decodeURIComponent(href).match(matrixLinkify.ELEMENT_URL_PATTERN)) {
+ return null;
+ } else {
+ return '_blank';
+ }
+ } catch (e) {
+ // malformed URI
}
}
return null;
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 9e27550499..577f23fa3a 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -164,8 +164,8 @@ export const SETTINGS: {[setting: string]: ISetting} = {
>;
},
image: require("../../res/img/betas/spaces.png"),
- feedbackSubheading: _td("You’re using an early version of Spaces, " +
- "your feedback will really help inform the next versions."),
+ feedbackSubheading: _td("Your feedback will help make spaces better. " +
+ "The more detail you can go into, the better."),
feedbackLabel: "spaces-feedback",
},
},
diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts
index 015ecca22e..d87c826cc2 100644
--- a/src/utils/permalinks/Permalinks.ts
+++ b/src/utils/permalinks/Permalinks.ts
@@ -346,9 +346,14 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string {
return permalink;
}
- const m = decodeURIComponent(permalink).match(matrixLinkify.ELEMENT_URL_PATTERN);
- if (m) {
- return m[1];
+ try {
+ const m = decodeURIComponent(permalink).match(matrixLinkify.ELEMENT_URL_PATTERN);
+ if (m) {
+ return m[1];
+ }
+ } catch (e) {
+ // Not a valid URI
+ return permalink;
}
// A bit of a hack to convert permalinks of unknown origin to Element links