Merge remote-tracking branch 'upstream/develop' into task/colors-4
commit
0a6e78fd38
|
@ -676,10 +676,57 @@ $hover-select-border: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo:hover {
|
.mx_ThreadInfo {
|
||||||
cursor: pointer;
|
height: 35px;
|
||||||
|
position: relative;
|
||||||
|
background-color: $system;
|
||||||
|
padding-left: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding-right: 16px;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $secondary-content;
|
||||||
|
box-sizing: border-box;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&:hover, &-active {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid $quinary-content;
|
||||||
|
padding-top: 7px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
padding-left: 11px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ThreadInfo_content {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ThreadInfo_thread-icon {
|
||||||
|
mask-image: url('$(res)/img/element-icons/thread-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
height: 16px;
|
||||||
|
min-width: 16px;
|
||||||
|
background-color: $secondary-content;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
.mx_ThreadInfo_threads-amount {
|
||||||
|
font-weight: 600;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.mx_ThreadView {
|
.mx_ThreadView {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 18 18"><path fill="#17191C" fill-rule="evenodd" d="M3 .25A2.75 2.75 0 0 0 .25 3v14a.75.75 0 0 0 1.2.6L4.916 15c.217-.162.48-.25.75-.25H15A2.75 2.75 0 0 0 17.75 12V3A2.75 2.75 0 0 0 15 .25H3ZM4.25 6A.75.75 0 0 1 5 5.25h8a.75.75 0 0 1 0 1.5H5A.75.75 0 0 1 4.25 6ZM5 8.25a.75.75 0 0 0 0 1.5h4a.75.75 0 1 0 0-1.5H5Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 428 B |
|
@ -460,7 +460,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// Checking if the message has a "parentEventId" as we do not
|
// Checking if the message has a "parentEventId" as we do not
|
||||||
// want to hide the root event of the thread
|
// want to hide the root event of the thread
|
||||||
if (mxEv.isThreadRoot && this.props.hideThreadedMessages
|
if (mxEv.isThreadRelation && this.props.hideThreadedMessages
|
||||||
&& SettingsStore.getValue("feature_thread")) {
|
&& SettingsStore.getValue("feature_thread")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,10 +48,8 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
replyToEvent?: MatrixEvent;
|
|
||||||
thread?: Thread;
|
thread?: Thread;
|
||||||
editState?: EditorStateTransfer;
|
editState?: EditorStateTransfer;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("structures.ThreadView")
|
@replaceableComponent("structures.ThreadView")
|
||||||
|
@ -69,11 +67,16 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this.setupThread(this.props.mxEvent);
|
this.setupThread(this.props.mxEvent);
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
|
||||||
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
room.on(ThreadEvent.New, this.onNewThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
this.teardownThread();
|
this.teardownThread();
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
room.on(ThreadEvent.New, this.onNewThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps) {
|
public componentDidUpdate(prevProps) {
|
||||||
|
@ -135,11 +138,17 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onNewThread = (thread: Thread) => {
|
||||||
|
if (thread.id === this.props.mxEvent.getId()) {
|
||||||
|
this.teardownThread();
|
||||||
|
this.setupThread(this.props.mxEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private updateThread = (thread?: Thread) => {
|
private updateThread = (thread?: Thread) => {
|
||||||
if (thread) {
|
if (thread) {
|
||||||
this.setState({
|
this.setState({
|
||||||
thread,
|
thread,
|
||||||
replyToEvent: thread.replyToEvent,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ import { getEventDisplayInfo } from '../../../utils/EventUtils';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion";
|
import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion";
|
||||||
import { dispatchShowThreadEvent } from '../../../dispatcher/dispatch-actions/threads';
|
import { dispatchShowThreadEvent } from '../../../dispatcher/dispatch-actions/threads';
|
||||||
|
import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore';
|
||||||
|
|
||||||
const eventTileTypes = {
|
const eventTileTypes = {
|
||||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||||
|
@ -475,6 +476,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
this.props.mxEvent.once(ThreadEvent.Ready, this.updateThread);
|
this.props.mxEvent.once(ThreadEvent.Ready, this.updateThread);
|
||||||
this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
|
this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
room.on(ThreadEvent.New, this.onNewThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateThread = (thread) => {
|
private updateThread = (thread) => {
|
||||||
|
@ -516,6 +520,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
this.props.mxEvent.off(ThreadEvent.Ready, this.updateThread);
|
this.props.mxEvent.off(ThreadEvent.Ready, this.updateThread);
|
||||||
this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
|
this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
room.off(ThreadEvent.New, this.onNewThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
@ -526,21 +533,39 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onNewThread = (thread: Thread) => {
|
||||||
|
if (thread.id === this.props.mxEvent.getId()) {
|
||||||
|
this.updateThread(thread);
|
||||||
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
room.off(ThreadEvent.New, this.onNewThread);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private renderThreadInfo(): React.ReactNode {
|
private renderThreadInfo(): React.ReactNode {
|
||||||
if (!SettingsStore.getValue("feature_thread")) {
|
if (!SettingsStore.getValue("feature_thread")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const thread = this.state.thread;
|
/**
|
||||||
|
* Accessing the threads value through the room due to a race condition
|
||||||
|
* that will be solved when there are proper backend support for threads
|
||||||
|
* We currently have no reliable way to discover than an event is a thread
|
||||||
|
* when we are at the sync stage
|
||||||
|
*/
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
const thread = room.threads.get(this.props.mxEvent.getId());
|
||||||
|
|
||||||
|
if (thread && !thread.ready) {
|
||||||
|
thread.addEvent(this.props.mxEvent, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (!thread || this.props.showThreadInfo === false || thread.length <= 1) {
|
if (!thread || this.props.showThreadInfo === false || thread.length <= 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatars = Array.from(thread.participants).map((mxId: string) => {
|
const threadMessagePreview = MessagePreviewStore.instance.generateThreadPreview(this.state.thread);
|
||||||
const member = room.getMember(mxId);
|
|
||||||
return <MemberAvatar key={member.userId} member={member} width={14} height={14} />;
|
if (!threadMessagePreview) return null;
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -549,10 +574,18 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
dispatchShowThreadEvent(this.props.mxEvent);
|
dispatchShowThreadEvent(this.props.mxEvent);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="mx_EventListSummary_avatars">
|
<span className="mx_ThreadInfo_thread-icon" />
|
||||||
{ avatars }
|
<span className="mx_ThreadInfo_threads-amount">
|
||||||
|
{ _t("%(count)s reply", {
|
||||||
|
count: thread.length - 1,
|
||||||
|
}) }
|
||||||
</span>
|
</span>
|
||||||
{ thread.length - 1 } { thread.length === 2 ? 'reply' : 'replies' }
|
<MemberAvatar member={thread.replyToEvent.sender} width={24} height={24} />
|
||||||
|
<div className="mx_ThreadInfo_content">
|
||||||
|
<span className="mx_ThreadInfo_message-preview">
|
||||||
|
{ threadMessagePreview }
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1551,6 +1551,8 @@
|
||||||
"Send as message": "Send as message",
|
"Send as message": "Send as message",
|
||||||
"Edit message": "Edit message",
|
"Edit message": "Edit message",
|
||||||
"Mod": "Mod",
|
"Mod": "Mod",
|
||||||
|
"%(count)s reply|other": "%(count)s replies",
|
||||||
|
"%(count)s reply|one": "%(count)s reply",
|
||||||
"This event could not be displayed": "This event could not be displayed",
|
"This event could not be displayed": "This event could not be displayed",
|
||||||
"Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.",
|
"Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.",
|
||||||
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.",
|
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.",
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { CallHangupEvent } from "./previews/CallHangupEvent";
|
||||||
import { StickerEventPreview } from "./previews/StickerEventPreview";
|
import { StickerEventPreview } from "./previews/StickerEventPreview";
|
||||||
import { ReactionEventPreview } from "./previews/ReactionEventPreview";
|
import { ReactionEventPreview } from "./previews/ReactionEventPreview";
|
||||||
import { UPDATE_EVENT } from "../AsyncStore";
|
import { UPDATE_EVENT } from "../AsyncStore";
|
||||||
|
import { Thread } from "matrix-js-sdk/src/models/thread";
|
||||||
|
|
||||||
// Emitted event for when a room's preview has changed. First argument will the room for which
|
// Emitted event for when a room's preview has changed. First argument will the room for which
|
||||||
// the change happened.
|
// the change happened.
|
||||||
|
@ -108,6 +109,15 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
||||||
return previews.get(inTagId);
|
return previews.get(inTagId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public generateThreadPreview(thread: Thread): string {
|
||||||
|
const lastEvent = thread.replyToEvent;
|
||||||
|
const previewDef = PREVIEWS[lastEvent.getType()];
|
||||||
|
// TODO: Handle case where we don't have
|
||||||
|
if (!previewDef) return '';
|
||||||
|
const previewText = previewDef.previewer.getTextFor(lastEvent, null, true);
|
||||||
|
return previewText ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
private async generatePreview(room: Room, tagId?: TagID) {
|
private async generatePreview(room: Room, tagId?: TagID) {
|
||||||
const events = room.timeline;
|
const events = room.timeline;
|
||||||
if (!events) return; // should only happen in tests
|
if (!events) return; // should only happen in tests
|
||||||
|
|
|
@ -23,7 +23,7 @@ import ReplyThread from "../../../components/views/elements/ReplyThread";
|
||||||
import { getHtmlText } from "../../../HtmlUtils";
|
import { getHtmlText } from "../../../HtmlUtils";
|
||||||
|
|
||||||
export class MessageEventPreview implements IPreview {
|
export class MessageEventPreview implements IPreview {
|
||||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
|
||||||
let eventContent = event.getContent();
|
let eventContent = event.getContent();
|
||||||
|
|
||||||
if (event.isRelation("m.replace")) {
|
if (event.isRelation("m.replace")) {
|
||||||
|
@ -64,7 +64,7 @@ export class MessageEventPreview implements IPreview {
|
||||||
return _t("* %(senderName)s %(emote)s", { senderName: getSenderName(event), emote: body });
|
return _t("* %(senderName)s %(emote)s", { senderName: getSenderName(event), emote: body });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||||
return body;
|
return body;
|
||||||
} else {
|
} else {
|
||||||
return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: body });
|
return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: body });
|
||||||
|
|
|
@ -23,7 +23,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
|
|
||||||
export class ReactionEventPreview implements IPreview {
|
export class ReactionEventPreview implements IPreview {
|
||||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
|
||||||
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
|
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
|
||||||
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
|
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export class ReactionEventPreview implements IPreview {
|
||||||
const reaction = relation.key;
|
const reaction = relation.key;
|
||||||
if (!reaction) return null; // invalid reaction (unknown format)
|
if (!reaction) return null; // invalid reaction (unknown format)
|
||||||
|
|
||||||
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||||
return reaction;
|
return reaction;
|
||||||
} else {
|
} else {
|
||||||
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });
|
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });
|
||||||
|
|
|
@ -21,11 +21,11 @@ import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
export class StickerEventPreview implements IPreview {
|
export class StickerEventPreview implements IPreview {
|
||||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
|
||||||
const stickerName = event.getContent()['body'];
|
const stickerName = event.getContent()['body'];
|
||||||
if (!stickerName) return null;
|
if (!stickerName) return null;
|
||||||
|
|
||||||
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||||
return stickerName;
|
return stickerName;
|
||||||
} else {
|
} else {
|
||||||
return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName });
|
return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName });
|
||||||
|
|
Loading…
Reference in New Issue