Merge branch 'develop' into travis/voice-messages/interrupt-text
commit
b2cb944d73
|
@ -354,7 +354,7 @@ $appearance-tab-border-color: $input-darker-bg-color;
|
|||
|
||||
// blur amounts for left left panel (only for element theme)
|
||||
:root {
|
||||
--lp-background-blur: 30px;
|
||||
--lp-background-blur: 40px;
|
||||
}
|
||||
$composer-shadow-color: rgba(0, 0, 0, 0.04);
|
||||
|
||||
|
|
|
@ -519,6 +519,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
inlineErrors: true,
|
||||
parentSpace: space,
|
||||
joinRule: !isPublic ? JoinRule.Restricted : undefined,
|
||||
suggested: true,
|
||||
});
|
||||
}));
|
||||
onFinished(filteredRoomNames.length > 0);
|
||||
|
|
|
@ -136,6 +136,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
<MessageComposer
|
||||
room={this.props.room}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
replyInThread={true}
|
||||
replyToEvent={this.state?.thread?.replyToEvent}
|
||||
showReplyPreview={false}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
|
|
|
@ -19,6 +19,7 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { UNSTABLE_ELEMENT_REPLY_IN_THREAD } from "matrix-js-sdk/src/@types/event";
|
||||
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { Layout } from "../../../settings/Layout";
|
||||
|
@ -206,15 +207,28 @@ export default class ReplyThread extends React.Component<IProps, IState> {
|
|||
return { body, html };
|
||||
}
|
||||
|
||||
public static makeReplyMixIn(ev: MatrixEvent) {
|
||||
public static makeReplyMixIn(ev: MatrixEvent, replyInThread: boolean) {
|
||||
if (!ev) return {};
|
||||
return {
|
||||
|
||||
const replyMixin = {
|
||||
'm.relates_to': {
|
||||
'm.in_reply_to': {
|
||||
'event_id': ev.getId(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
* Rendering hint for threads, only attached if true to make
|
||||
* sure that Element does not start sending that property for all events
|
||||
*/
|
||||
if (replyInThread) {
|
||||
const inReplyTo = replyMixin['m.relates_to']['m.in_reply_to'];
|
||||
inReplyTo[UNSTABLE_ELEMENT_REPLY_IN_THREAD.name] = replyInThread;
|
||||
}
|
||||
|
||||
return replyMixin;
|
||||
}
|
||||
|
||||
public static makeThread(
|
||||
|
|
|
@ -128,7 +128,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
private onImageEnter = (e: React.MouseEvent<HTMLImageElement>): void => {
|
||||
this.setState({ hover: true });
|
||||
|
||||
if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||
if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifs")) {
|
||||
return;
|
||||
}
|
||||
const imgElement = e.currentTarget;
|
||||
|
@ -138,7 +138,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
private onImageLeave = (e: React.MouseEvent<HTMLImageElement>): void => {
|
||||
this.setState({ hover: false });
|
||||
|
||||
if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||
if (!this.state.showImage || !this.isGif() || SettingsStore.getValue("autoplayGifs")) {
|
||||
return;
|
||||
}
|
||||
const imgElement = e.currentTarget;
|
||||
|
@ -387,7 +387,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
showPlaceholder = false; // because we're hiding the image, so don't show the placeholder.
|
||||
}
|
||||
|
||||
if (this.isGif() && !SettingsStore.getValue("autoplayGifsAndVideos") && !this.state.hover) {
|
||||
if (this.isGif() && !SettingsStore.getValue("autoplayGifs") && !this.state.hover) {
|
||||
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
|
||||
}
|
||||
|
||||
|
@ -487,7 +487,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
const contentUrl = this.getContentUrl();
|
||||
let thumbUrl;
|
||||
if (this.isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||
if (this.isGif() && SettingsStore.getValue("autoplayGifs")) {
|
||||
thumbUrl = contentUrl;
|
||||
} else {
|
||||
thumbUrl = this.getThumbUrl();
|
||||
|
|
|
@ -145,7 +145,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
|||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const autoplay = SettingsStore.getValue("autoplayGifsAndVideos") as boolean;
|
||||
const autoplay = SettingsStore.getValue("autoplayVideo") as boolean;
|
||||
this.loadBlurhash();
|
||||
|
||||
if (this.props.mediaEventHelper.media.isEncrypted && this.state.decryptedUrl === null) {
|
||||
|
@ -209,7 +209,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
|||
|
||||
render() {
|
||||
const content = this.props.mxEvent.getContent();
|
||||
const autoplay = SettingsStore.getValue("autoplayGifsAndVideos");
|
||||
const autoplay = SettingsStore.getValue("autoplayVideo");
|
||||
|
||||
if (this.state.error !== null) {
|
||||
return (
|
||||
|
|
|
@ -43,11 +43,6 @@ import QuestionDialog from "../dialogs/QuestionDialog";
|
|||
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
function eventIsReply(mxEvent: MatrixEvent): boolean {
|
||||
const relatesTo = mxEvent.getContent()["m.relates_to"];
|
||||
return !!(relatesTo && relatesTo["m.in_reply_to"]);
|
||||
}
|
||||
|
||||
function getHtmlReplyFallback(mxEvent: MatrixEvent): string {
|
||||
const html = mxEvent.getContent().formatted_body;
|
||||
if (!html) {
|
||||
|
@ -72,7 +67,7 @@ function createEditContent(model: EditorModel, editedEvent: MatrixEvent): IConte
|
|||
if (isEmote) {
|
||||
model = stripEmoteCommand(model);
|
||||
}
|
||||
const isReply = eventIsReply(editedEvent);
|
||||
const isReply = !!editedEvent.replyEventId;
|
||||
let plainPrefix = "";
|
||||
let htmlPrefix = "";
|
||||
|
||||
|
|
|
@ -183,6 +183,7 @@ interface IProps {
|
|||
resizeNotifier: ResizeNotifier;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
replyToEvent?: MatrixEvent;
|
||||
replyInThread?: boolean;
|
||||
showReplyPreview?: boolean;
|
||||
e2eStatus?: E2EStatus;
|
||||
compact?: boolean;
|
||||
|
@ -204,6 +205,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
private voiceRecordingButton: VoiceRecordComposerTile;
|
||||
|
||||
static defaultProps = {
|
||||
replyInThread: false,
|
||||
showReplyPreview: true,
|
||||
compact: false,
|
||||
};
|
||||
|
@ -383,6 +385,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
room={this.props.room}
|
||||
placeholder={this.renderPlaceholderText()}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
replyInThread={this.props.replyInThread}
|
||||
replyToEvent={this.props.replyToEvent}
|
||||
onChange={this.onChange}
|
||||
disabled={this.state.haveRecording}
|
||||
|
|
|
@ -57,15 +57,16 @@ import { ActionPayload } from "../../../dispatcher/payloads";
|
|||
|
||||
function addReplyToMessageContent(
|
||||
content: IContent,
|
||||
repliedToEvent: MatrixEvent,
|
||||
replyToEvent: MatrixEvent,
|
||||
replyInThread: boolean,
|
||||
permalinkCreator: RoomPermalinkCreator,
|
||||
): void {
|
||||
const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
|
||||
const replyContent = ReplyThread.makeReplyMixIn(replyToEvent, replyInThread);
|
||||
Object.assign(content, replyContent);
|
||||
|
||||
// Part of Replies fallback support - prepend the text we're sending
|
||||
// with the text we're replying to
|
||||
const nestedReply = ReplyThread.getNestedReplyText(repliedToEvent, permalinkCreator);
|
||||
const nestedReply = ReplyThread.getNestedReplyText(replyToEvent, permalinkCreator);
|
||||
if (nestedReply) {
|
||||
if (content.formatted_body) {
|
||||
content.formatted_body = nestedReply.html + content.formatted_body;
|
||||
|
@ -77,8 +78,9 @@ function addReplyToMessageContent(
|
|||
// exported for tests
|
||||
export function createMessageContent(
|
||||
model: EditorModel,
|
||||
permalinkCreator: RoomPermalinkCreator,
|
||||
replyToEvent: MatrixEvent,
|
||||
replyInThread: boolean,
|
||||
permalinkCreator: RoomPermalinkCreator,
|
||||
): IContent {
|
||||
const isEmote = containsEmote(model);
|
||||
if (isEmote) {
|
||||
|
@ -101,7 +103,7 @@ export function createMessageContent(
|
|||
}
|
||||
|
||||
if (replyToEvent) {
|
||||
addReplyToMessageContent(content, replyToEvent, permalinkCreator);
|
||||
addReplyToMessageContent(content, replyToEvent, replyInThread, permalinkCreator);
|
||||
}
|
||||
|
||||
return content;
|
||||
|
@ -129,6 +131,7 @@ interface IProps {
|
|||
room: Room;
|
||||
placeholder?: string;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
replyInThread?: boolean;
|
||||
replyToEvent?: MatrixEvent;
|
||||
disabled?: boolean;
|
||||
onChange?(model: EditorModel): void;
|
||||
|
@ -357,7 +360,12 @@ export default class SendMessageComposer extends React.Component<IProps> {
|
|||
if (cmd.category === CommandCategories.messages) {
|
||||
content = await this.runSlashCommand(cmd, args);
|
||||
if (replyToEvent) {
|
||||
addReplyToMessageContent(content, replyToEvent, this.props.permalinkCreator);
|
||||
addReplyToMessageContent(
|
||||
content,
|
||||
replyToEvent,
|
||||
this.props.replyInThread,
|
||||
this.props.permalinkCreator,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.runSlashCommand(cmd, args);
|
||||
|
@ -400,7 +408,12 @@ export default class SendMessageComposer extends React.Component<IProps> {
|
|||
const startTime = CountlyAnalytics.getTimestamp();
|
||||
const { roomId } = this.props.room;
|
||||
if (!content) {
|
||||
content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent);
|
||||
content = createMessageContent(
|
||||
this.model,
|
||||
replyToEvent,
|
||||
this.props.replyInThread,
|
||||
this.props.permalinkCreator,
|
||||
);
|
||||
}
|
||||
// don't bother sending an empty message
|
||||
if (!content.body.trim()) return;
|
||||
|
|
|
@ -172,7 +172,8 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
];
|
||||
static IMAGES_AND_VIDEOS_SETTINGS = [
|
||||
'urlPreviewsEnabled',
|
||||
'autoplayGifsAndVideos',
|
||||
'autoplayGifs',
|
||||
'autoplayVideo',
|
||||
'showImages',
|
||||
];
|
||||
static TIMELINE_SETTINGS = [
|
||||
|
|
|
@ -62,6 +62,8 @@ export interface IOpts {
|
|||
roomType?: RoomType | string;
|
||||
historyVisibility?: HistoryVisibility;
|
||||
parentSpace?: Room;
|
||||
// contextually only makes sense if parentSpace is specified, if true then will be added to parentSpace as suggested
|
||||
suggested?: boolean;
|
||||
joinRule?: JoinRule;
|
||||
}
|
||||
|
||||
|
@ -228,7 +230,7 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
|
|||
}
|
||||
}).then(() => {
|
||||
if (opts.parentSpace) {
|
||||
return SpaceStore.instance.addRoomToSpace(opts.parentSpace, roomId, [client.getDomain()], true);
|
||||
return SpaceStore.instance.addRoomToSpace(opts.parentSpace, roomId, [client.getDomain()], opts.suggested);
|
||||
}
|
||||
if (opts.associatedWithCommunity) {
|
||||
return GroupStore.addRoomToGroup(opts.associatedWithCommunity, roomId, false);
|
||||
|
|
|
@ -834,7 +834,8 @@
|
|||
"Show read receipts sent by other users": "Show read receipts sent by other users",
|
||||
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)",
|
||||
"Always show message timestamps": "Always show message timestamps",
|
||||
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
|
||||
"Autoplay GIFs": "Autoplay GIFs",
|
||||
"Autoplay videos": "Autoplay videos",
|
||||
"Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
|
||||
"Expand code blocks by default": "Expand code blocks by default",
|
||||
"Show line numbers in code blocks": "Show line numbers in code blocks",
|
||||
|
|
|
@ -393,9 +393,14 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
displayName: _td('Always show message timestamps'),
|
||||
default: false,
|
||||
},
|
||||
"autoplayGifsAndVideos": {
|
||||
"autoplayGifs": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
displayName: _td('Autoplay GIFs and videos'),
|
||||
displayName: _td('Autoplay GIFs'),
|
||||
default: false,
|
||||
},
|
||||
"autoplayVideo": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
displayName: _td('Autoplay videos'),
|
||||
default: false,
|
||||
},
|
||||
"enableSyntaxHighlightLanguageDetection": {
|
||||
|
|
|
@ -110,6 +110,21 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
return content ? content['enabled'] : null;
|
||||
}
|
||||
|
||||
// Special case for autoplaying videos and GIFs
|
||||
if (["autoplayGifs", "autoplayVideo"].includes(settingName)) {
|
||||
const settings = this.getSettings() || {};
|
||||
const value = settings[settingName];
|
||||
// Fallback to old combined setting
|
||||
if (value === null || value === undefined) {
|
||||
const oldCombinedValue = settings["autoplayGifsAndVideos"];
|
||||
// Write, so that we can remove this in the future
|
||||
this.setValue("autoplayGifs", roomId, oldCombinedValue);
|
||||
this.setValue("autoplayVideo", roomId, oldCombinedValue);
|
||||
return oldCombinedValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const settings = this.getSettings() || {};
|
||||
let preferredValue = settings[settingName];
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ describe('<SendMessageComposer/>', () => {
|
|||
const model = new EditorModel([], createPartCreator(), createRenderer());
|
||||
model.update("hello world", "insertText", { offset: 11, atNodeEnd: true });
|
||||
|
||||
const content = createMessageContent(model, permalinkCreator);
|
||||
const content = createMessageContent(model, null, false, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "hello world",
|
||||
|
@ -58,7 +58,7 @@ describe('<SendMessageComposer/>', () => {
|
|||
const model = new EditorModel([], createPartCreator(), createRenderer());
|
||||
model.update("hello *world*", "insertText", { offset: 13, atNodeEnd: true });
|
||||
|
||||
const content = createMessageContent(model, permalinkCreator);
|
||||
const content = createMessageContent(model, null, false, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "hello *world*",
|
||||
|
@ -72,7 +72,7 @@ describe('<SendMessageComposer/>', () => {
|
|||
const model = new EditorModel([], createPartCreator(), createRenderer());
|
||||
model.update("/me blinks __quickly__", "insertText", { offset: 22, atNodeEnd: true });
|
||||
|
||||
const content = createMessageContent(model, permalinkCreator);
|
||||
const content = createMessageContent(model, null, false, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "blinks __quickly__",
|
||||
|
@ -86,7 +86,7 @@ describe('<SendMessageComposer/>', () => {
|
|||
const model = new EditorModel([], createPartCreator(), createRenderer());
|
||||
model.update("//dev/null is my favourite place", "insertText", { offset: 32, atNodeEnd: true });
|
||||
|
||||
const content = createMessageContent(model, permalinkCreator);
|
||||
const content = createMessageContent(model, null, false, permalinkCreator);
|
||||
|
||||
expect(content).toEqual({
|
||||
body: "/dev/null is my favourite place",
|
||||
|
|
Loading…
Reference in New Issue