mirror of https://github.com/vector-im/riot-web
Introduce an entirely new system for handling message preview copy
This reverts earlier changes made to textForEvent as they are no longer needed. This also implements an entire tree of textForEvent-like behaviour as the previews need to be different, which is easiest done with its own stack.pull/21833/head
parent
752b2acc56
commit
acf56559e1
|
@ -265,22 +265,13 @@ function textForServerACLEvent(ev) {
|
|||
return text + changes.join(" ");
|
||||
}
|
||||
|
||||
function textForMessageEvent(ev, skipUserPrefix) {
|
||||
function textForMessageEvent(ev) {
|
||||
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
let message = senderDisplayName + ': ' + ev.getContent().body;
|
||||
if (skipUserPrefix) {
|
||||
message = ev.getContent().body;
|
||||
if (ev.getContent().msgtype === "m.emote") {
|
||||
message = senderDisplayName + " " + message;
|
||||
} else if (ev.getContent().msgtype === "m.image") {
|
||||
message = _t('sent an image.');
|
||||
}
|
||||
} else {
|
||||
if (ev.getContent().msgtype === "m.emote") {
|
||||
message = "* " + senderDisplayName + " " + message;
|
||||
} else if (ev.getContent().msgtype === "m.image") {
|
||||
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName});
|
||||
}
|
||||
if (ev.getContent().msgtype === "m.emote") {
|
||||
message = "* " + senderDisplayName + " " + message;
|
||||
} else if (ev.getContent().msgtype === "m.image") {
|
||||
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName});
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
@ -621,8 +612,8 @@ for (const evType of ALL_RULE_TYPES) {
|
|||
stateHandlers[evType] = textForMjolnirEvent;
|
||||
}
|
||||
|
||||
export function textForEvent(ev, skipUserPrefix) {
|
||||
export function textForEvent(ev) {
|
||||
const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()];
|
||||
if (handler) return handler(ev, skipUserPrefix);
|
||||
if (handler) return handler(ev);
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
|||
let messagePreview = null;
|
||||
if (this.props.showMessagePreview && !this.props.isMinimized) {
|
||||
// The preview store heavily caches this info, so should be safe to hammer.
|
||||
const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room);
|
||||
const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag);
|
||||
|
||||
// Only show the preview if there is one to show.
|
||||
if (text) {
|
||||
|
|
|
@ -247,7 +247,6 @@
|
|||
"%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.",
|
||||
"%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.",
|
||||
"%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.",
|
||||
"sent an image.": "sent an image.",
|
||||
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.",
|
||||
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.",
|
||||
"%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.",
|
||||
|
@ -421,12 +420,65 @@
|
|||
"Restart": "Restart",
|
||||
"Upgrade your Riot": "Upgrade your Riot",
|
||||
"A new version of Riot is available!": "A new version of Riot is available!",
|
||||
"You: %(message)s": "You: %(message)s",
|
||||
"Guest": "Guest",
|
||||
"There was an error joining the room": "There was an error joining the room",
|
||||
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
|
||||
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
|
||||
"Failed to join room": "Failed to join room",
|
||||
"You joined the call": "You joined the call",
|
||||
"%(senderName)s joined the call": "%(senderName)s joined the call",
|
||||
"Call in progress": "Call in progress",
|
||||
"You left the call": "You left the call",
|
||||
"%(senderName)s left the call": "%(senderName)s left the call",
|
||||
"Call ended": "Call ended",
|
||||
"You started a call": "You started a call",
|
||||
"%(senderName)s started a call": "%(senderName)s started a call",
|
||||
"Waiting for answer": "Waiting for answer",
|
||||
"%(senderName)s is calling": "%(senderName)s is calling",
|
||||
"You created the room": "You created the room",
|
||||
"%(senderName)s created the room": "%(senderName)s created the room",
|
||||
"You made the chat encrypted": "You made the chat encrypted",
|
||||
"%(senderName)s made the chat encrypted": "%(senderName)s made the chat encrypted",
|
||||
"You made history visible to new members": "You made history visible to new members",
|
||||
"%(senderName)s made history visible to new members": "%(senderName)s made history visible to new members",
|
||||
"You made history visible to anyone": "You made history visible to anyone",
|
||||
"%(senderName)s made history visible to anyone": "%(senderName)s made history visible to anyone",
|
||||
"You made history visible to future members": "You made history visible to future members",
|
||||
"%(senderName)s made history visible to future members": "%(senderName)s made history visible to future members",
|
||||
"You were invited": "You were invited",
|
||||
"%(targetName)s was invited": "%(targetName)s was invited",
|
||||
"You left": "You left",
|
||||
"%(targetName)s left": "%(targetName)s left",
|
||||
"You were kicked (%(reason)s)": "You were kicked (%(reason)s)",
|
||||
"%(targetName)s was kicked (%(reason)s)": "%(targetName)s was kicked (%(reason)s)",
|
||||
"You were kicked": "You were kicked",
|
||||
"%(targetName)s was kicked": "%(targetName)s was kicked",
|
||||
"You rejected the invite": "You rejected the invite",
|
||||
"%(targetName)s rejected the invite": "%(targetName)s rejected the invite",
|
||||
"You were uninvited": "You were uninvited",
|
||||
"%(targetName)s was uninvited": "%(targetName)s was uninvited",
|
||||
"You were banned (%(reason)s)": "You were banned (%(reason)s)",
|
||||
"%(targetName)s was banned (%(reason)s)": "%(targetName)s was banned (%(reason)s)",
|
||||
"You were banned": "You were banned",
|
||||
"%(targetName)s was banned": "%(targetName)s was banned",
|
||||
"You joined": "You joined",
|
||||
"%(targetName)s joined": "%(targetName)s joined",
|
||||
"You changed your name": "You changed your name",
|
||||
"%(targetName)s changed their name": "%(targetName)s changed their name",
|
||||
"You changed your avatar": "You changed your avatar",
|
||||
"%(targetName)s changed their avatar": "%(targetName)s changed their avatar",
|
||||
"%(senderName)s %(emote)s": "%(senderName)s %(emote)s",
|
||||
"%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
|
||||
"You changed the room name": "You changed the room name",
|
||||
"%(senderName)s changed the room name": "%(senderName)s changed the room name",
|
||||
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
|
||||
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
|
||||
"You uninvited %(targetName)s": "You uninvited %(targetName)s",
|
||||
"%(senderName)s uninvited %(targetName)s": "%(senderName)s uninvited %(targetName)s",
|
||||
"You invited %(targetName)s": "You invited %(targetName)s",
|
||||
"%(senderName)s invited %(targetName)s": "%(senderName)s invited %(targetName)s",
|
||||
"You changed the room topic": "You changed the room topic",
|
||||
"%(senderName)s changed the room topic": "%(senderName)s changed the room topic",
|
||||
"Font scaling": "Font scaling",
|
||||
"Message Pinning": "Message Pinning",
|
||||
"Custom user status messages": "Custom user status messages",
|
||||
|
|
|
@ -19,32 +19,86 @@ import { ActionPayload } from "../../dispatcher/payloads";
|
|||
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { RoomListStoreTempProxy } from "./RoomListStoreTempProxy";
|
||||
import { textForEvent } from "../../TextForEvent";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { MessageEventPreview } from "./previews/MessageEventPreview";
|
||||
import { NameEventPreview } from "./previews/NameEventPreview";
|
||||
import { TagID } from "./models";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
import { TopicEventPreview } from "./previews/TopicEventPreview";
|
||||
import { MembershipEventPreview } from "./previews/MembershipEventPreview";
|
||||
import { HistoryVisibilityEventPreview } from "./previews/HistoryVisibilityEventPreview";
|
||||
import { CallInviteEventPreview } from "./previews/CallInviteEventPreview";
|
||||
import { CallAnswerEventPreview } from "./previews/CallAnswerEventPreview";
|
||||
import { CallHangupEvent } from "./previews/CallHangupEvent";
|
||||
import { EncryptionEventPreview } from "./previews/EncryptionEventPreview";
|
||||
import { ThirdPartyInviteEventPreview } from "./previews/ThirdPartyInviteEventPreview";
|
||||
import { StickerEventPreview } from "./previews/StickerEventPreview";
|
||||
import { ReactionEventPreview } from "./previews/ReactionEventPreview";
|
||||
import { CreationEventPreview } from "./previews/CreationEventPreview";
|
||||
|
||||
const PREVIEWABLE_EVENTS = [
|
||||
// This is the same list from RiotX
|
||||
{type: "m.room.message", isState: false},
|
||||
{type: "m.room.name", isState: true},
|
||||
{type: "m.room.topic", isState: true},
|
||||
{type: "m.room.member", isState: true},
|
||||
{type: "m.room.history_visibility", isState: true},
|
||||
{type: "m.call.invite", isState: false},
|
||||
{type: "m.call.hangup", isState: false},
|
||||
{type: "m.call.answer", isState: false},
|
||||
{type: "m.room.encrypted", isState: false},
|
||||
{type: "m.room.encryption", isState: true},
|
||||
{type: "m.room.third_party_invite", isState: true},
|
||||
{type: "m.sticker", isState: false},
|
||||
{type: "m.room.create", isState: true},
|
||||
];
|
||||
const PREVIEWS = {
|
||||
'm.room.message': {
|
||||
isState: false,
|
||||
previewer: new MessageEventPreview(),
|
||||
},
|
||||
'm.room.name': {
|
||||
isState: true,
|
||||
previewer: new NameEventPreview(),
|
||||
},
|
||||
'm.room.topic': {
|
||||
isState: true,
|
||||
previewer: new TopicEventPreview(),
|
||||
},
|
||||
'm.room.member': {
|
||||
isState: true,
|
||||
previewer: new MembershipEventPreview(),
|
||||
},
|
||||
'm.room.history_visibility': {
|
||||
isState: true,
|
||||
previewer: new HistoryVisibilityEventPreview(),
|
||||
},
|
||||
'm.call.invite': {
|
||||
isState: false,
|
||||
previewer: new CallInviteEventPreview(),
|
||||
},
|
||||
'm.call.answer': {
|
||||
isState: false,
|
||||
previewer: new CallAnswerEventPreview(),
|
||||
},
|
||||
'm.call.hangup': {
|
||||
isState: false,
|
||||
previewer: new CallHangupEvent(),
|
||||
},
|
||||
'm.room.encryption': {
|
||||
isState: true,
|
||||
previewer: new EncryptionEventPreview(),
|
||||
},
|
||||
'm.room.third_party_invite': {
|
||||
isState: true,
|
||||
previewer: new ThirdPartyInviteEventPreview(),
|
||||
},
|
||||
'm.sticker': {
|
||||
isState: false,
|
||||
previewer: new StickerEventPreview(),
|
||||
},
|
||||
'm.reaction': {
|
||||
isState: false,
|
||||
previewer: new ReactionEventPreview(),
|
||||
},
|
||||
'm.room.create': {
|
||||
isState: true,
|
||||
previewer: new CreationEventPreview(),
|
||||
},
|
||||
};
|
||||
|
||||
// The maximum number of events we're willing to look back on to get a preview.
|
||||
const MAX_EVENTS_BACKWARDS = 50;
|
||||
|
||||
// type merging ftw
|
||||
type TAG_ANY = "im.vector.any";
|
||||
const TAG_ANY: TAG_ANY = "im.vector.any";
|
||||
|
||||
interface IState {
|
||||
[roomId: string]: string | null; // null indicates the preview is empty
|
||||
[roomId: string]: Map<TagID | TAG_ANY, string | null>; // null indicates the preview is empty / irrelevant
|
||||
}
|
||||
|
||||
export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
||||
|
@ -61,39 +115,76 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
|||
/**
|
||||
* Gets the pre-translated preview for a given room
|
||||
* @param room The room to get the preview for.
|
||||
* @param inTagId The tag ID in which the room resides
|
||||
* @returns The preview, or null if none present.
|
||||
*/
|
||||
public getPreviewForRoom(room: Room): string {
|
||||
public getPreviewForRoom(room: Room, inTagId: TagID): string {
|
||||
if (!room) return null; // invalid room, just return nothing
|
||||
|
||||
// It's faster to do a lookup this way than it is to use Object.keys().includes()
|
||||
// We only want to generate a preview if there's one actually missing and not explicitly
|
||||
// set as 'none'.
|
||||
const val = this.state[room.roomId];
|
||||
if (val !== null && typeof(val) !== "string") {
|
||||
this.generatePreview(room);
|
||||
}
|
||||
if (!val) this.generatePreview(room, inTagId);
|
||||
|
||||
return this.state[room.roomId];
|
||||
const previews = this.state[room.roomId];
|
||||
if (!previews) return null;
|
||||
|
||||
if (!previews.has(inTagId)) {
|
||||
return previews.get(TAG_ANY);
|
||||
}
|
||||
return previews.get(inTagId);
|
||||
}
|
||||
|
||||
private generatePreview(room: Room) {
|
||||
private generatePreview(room: Room, tagId?: TagID) {
|
||||
const events = room.timeline;
|
||||
if (!events) return; // should only happen in tests
|
||||
|
||||
let map = this.state[room.roomId];
|
||||
if (!map) {
|
||||
map = new Map<TagID | TAG_ANY, string | null>();
|
||||
|
||||
// We set the state later with the map, so no need to send an update now
|
||||
}
|
||||
|
||||
// Set the tags so we know what to generate
|
||||
if (!map.has(TAG_ANY)) map.set(TAG_ANY, null);
|
||||
if (tagId && !map.has(tagId)) map.set(tagId, null);
|
||||
|
||||
let changed = false;
|
||||
for (let i = events.length - 1; i >= 0; i--) {
|
||||
if (i === events.length - MAX_EVENTS_BACKWARDS) return; // limit reached
|
||||
|
||||
const event = events[i];
|
||||
const preview = this.generatePreviewForEvent(event);
|
||||
if (preview.isPreviewable) {
|
||||
// noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls
|
||||
this.updateState({[room.roomId]: preview.preview});
|
||||
return; // break - we found some text
|
||||
const previewDef = PREVIEWS[event.getType()];
|
||||
if (!previewDef) continue;
|
||||
if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue;
|
||||
|
||||
const anyPreview = previewDef.previewer.getTextFor(event, null);
|
||||
if (!anyPreview) continue; // not previewable for some reason
|
||||
|
||||
changed = changed || anyPreview !== map.get(TAG_ANY);
|
||||
map.set(TAG_ANY, anyPreview);
|
||||
|
||||
const tagsToGenerate = Array.from(map.keys()).filter(t => t !== TAG_ANY); // we did the any tag above
|
||||
for (const genTagId of tagsToGenerate) {
|
||||
const realTagId: TagID = genTagId === TAG_ANY ? null : genTagId;
|
||||
const preview = previewDef.previewer.getTextFor(event, realTagId);
|
||||
if (preview === anyPreview) {
|
||||
changed = changed || anyPreview !== map.get(genTagId);
|
||||
map.delete(genTagId);
|
||||
} else {
|
||||
changed = changed || preview !== map.get(genTagId);
|
||||
map.set(genTagId, preview);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
// Update state for good measure - causes emit for update
|
||||
// noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls
|
||||
this.updateState({[room.roomId]: map});
|
||||
}
|
||||
return; // we're done
|
||||
}
|
||||
|
||||
// if we didn't find anything, subscribe ourselves to an update
|
||||
// At this point, we didn't generate a preview so clear it
|
||||
// noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls
|
||||
this.updateState({[room.roomId]: null});
|
||||
}
|
||||
|
@ -107,28 +198,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
|||
if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') {
|
||||
const event = payload.event; // TODO: Type out the dispatcher
|
||||
if (!Object.keys(this.state).includes(event.getRoomId())) return; // not important
|
||||
|
||||
const preview = this.generatePreviewForEvent(event);
|
||||
if (preview.isPreviewable) {
|
||||
await this.updateState({[event.getRoomId()]: preview.preview});
|
||||
return; // break - we found some text
|
||||
}
|
||||
this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY);
|
||||
}
|
||||
}
|
||||
|
||||
private generatePreviewForEvent(event: MatrixEvent): { isPreviewable: boolean, preview: string } {
|
||||
if (PREVIEWABLE_EVENTS.some(p => p.type === event.getType() && p.isState === event.isState())) {
|
||||
const isSelf = event.getSender() === this.matrixClient.getUserId();
|
||||
let text = textForEvent(event, /*skipUserPrefix=*/isSelf);
|
||||
if (!text || text.trim().length === 0) text = null; // force null if useless to us
|
||||
if (text && isSelf) {
|
||||
// XXX: i18n doesn't really work here if the language doesn't support prefixing.
|
||||
// We'd ideally somehow route the `You:` bit to the textForEvent call, however
|
||||
// threading that through is non-trivial.
|
||||
text = _t("You: %(message)s", {message: text});
|
||||
}
|
||||
return {isPreviewable: true, preview: text};
|
||||
}
|
||||
return {isPreviewable: false, preview: null};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class CallAnswerEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||
if (isSelf(event)) {
|
||||
return _t("You joined the call");
|
||||
} else {
|
||||
return _t("%(senderName)s joined the call", {senderName: getSenderName(event)});
|
||||
}
|
||||
} else {
|
||||
return _t("Call in progress");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class CallHangupEvent implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||
if (isSelf(event)) {
|
||||
return _t("You left the call");
|
||||
} else {
|
||||
return _t("%(senderName)s left the call", {senderName: getSenderName(event)});
|
||||
}
|
||||
} else {
|
||||
return _t("Call ended");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class CallInviteEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||
if (isSelf(event)) {
|
||||
return _t("You started a call");
|
||||
} else {
|
||||
return _t("%(senderName)s started a call", {senderName: getSenderName(event)});
|
||||
}
|
||||
} else {
|
||||
if (isSelf(event)) {
|
||||
return _t("Waiting for answer");
|
||||
} else {
|
||||
return _t("%(senderName)s is calling", {senderName: getSenderName(event)});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class CreationEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (isSelf(event)) {
|
||||
return _t("You created the room");
|
||||
} else {
|
||||
return _t("%(senderName)s created the room", {senderName: getSenderName(event)});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class EncryptionEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (isSelf(event)) {
|
||||
return _t("You made the chat encrypted");
|
||||
} else {
|
||||
return _t("%(senderName)s made the chat encrypted", {senderName: getSenderName(event)});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class HistoryVisibilityEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
const visibility = event.getContent()['history_visibility'];
|
||||
const isUs = isSelf(event);
|
||||
|
||||
if (visibility === 'invited' || visibility === 'joined') {
|
||||
return isUs
|
||||
? _t("You made history visible to new members")
|
||||
: _t("%(senderName)s made history visible to new members", {senderName: getSenderName(event)});
|
||||
} else if (visibility === 'world_readable') {
|
||||
return isUs
|
||||
? _t("You made history visible to anyone")
|
||||
: _t("%(senderName)s made history visible to anyone", {senderName: getSenderName(event)});
|
||||
} else { // shared, default
|
||||
return isUs
|
||||
? _t("You made history visible to future members")
|
||||
: _t("%(senderName)s made history visible to future members", {senderName: getSenderName(event)});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { TagID } from "../models";
|
||||
|
||||
/**
|
||||
* Represents an event preview.
|
||||
*/
|
||||
export interface IPreview {
|
||||
/**
|
||||
* Gets the text which represents the event as a preview.
|
||||
* @param event The event to preview.
|
||||
* @param tagId Optional. The tag where the room the event was sent in resides.
|
||||
* @returns The preview.
|
||||
*/
|
||||
getTextFor(event: MatrixEvent, tagId?: TagID): string;
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getTargetName, isSelfTarget } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class MembershipEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
const newMembership = event.getContent()['membership'];
|
||||
const oldMembership = event.getPrevContent()['membership'];
|
||||
const reason = event.getContent()['reason'];
|
||||
const isUs = isSelfTarget(event);
|
||||
|
||||
if (newMembership === 'invite') {
|
||||
return isUs
|
||||
? _t("You were invited")
|
||||
: _t("%(targetName)s was invited", {targetName: getTargetName(event)});
|
||||
} else if (newMembership === 'leave' && oldMembership !== 'invite') {
|
||||
if (event.getSender() === event.getStateKey()) {
|
||||
return isUs
|
||||
? _t("You left")
|
||||
: _t("%(targetName)s left", {targetName: getTargetName(event)});
|
||||
} else {
|
||||
if (reason) {
|
||||
return isUs
|
||||
? _t("You were kicked (%(reason)s)", {reason})
|
||||
: _t("%(targetName)s was kicked (%(reason)s)", {targetName: getTargetName(event), reason});
|
||||
} else {
|
||||
return isUs
|
||||
? _t("You were kicked")
|
||||
: _t("%(targetName)s was kicked", {targetName: getTargetName(event)});
|
||||
}
|
||||
}
|
||||
} else if (newMembership === 'leave' && oldMembership === 'invite') {
|
||||
if (event.getSender() === event.getStateKey()) {
|
||||
return isUs
|
||||
? _t("You rejected the invite")
|
||||
: _t("%(targetName)s rejected the invite", {targetName: getTargetName(event)});
|
||||
} else {
|
||||
return isUs
|
||||
? _t("You were uninvited")
|
||||
: _t("%(targetName)s was uninvited", {targetName: getTargetName(event)});
|
||||
}
|
||||
} else if (newMembership === 'ban') {
|
||||
if (reason) {
|
||||
return isUs
|
||||
? _t("You were banned (%(reason)s)", {reason})
|
||||
: _t("%(targetName)s was banned (%(reason)s)", {targetName: getTargetName(event), reason});
|
||||
} else {
|
||||
return isUs
|
||||
? _t("You were banned")
|
||||
: _t("%(targetName)s was banned", {targetName: getTargetName(event)});
|
||||
}
|
||||
} else if (newMembership === 'join' && oldMembership !== 'join') {
|
||||
return isUs
|
||||
? _t("You joined")
|
||||
: _t("%(targetName)s joined", {targetName: getTargetName(event)});
|
||||
} else {
|
||||
const isDisplayNameChange = event.getContent()['displayname'] !== event.getPrevContent()['displayname'];
|
||||
const isAvatarChange = event.getContent()['avatar_url'] !== event.getPrevContent()['avatar_url'];
|
||||
if (isDisplayNameChange) {
|
||||
return isUs
|
||||
? _t("You changed your name")
|
||||
: _t("%(targetName)s changed their name", {targetName: getTargetName(event)});
|
||||
} else if (isAvatarChange) {
|
||||
return isUs
|
||||
? _t("You changed your avatar")
|
||||
: _t("%(targetName)s changed their avatar", {targetName: getTargetName(event)});
|
||||
} else {
|
||||
return null; // no change
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||
import ReplyThread from "../../../components/views/elements/ReplyThread";
|
||||
|
||||
export class MessageEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
let eventContent = event.getContent();
|
||||
|
||||
if (event.isRelation("m.replace")) {
|
||||
// It's an edit, generate the preview on the new text
|
||||
eventContent = event.getContent()['m.new_content'];
|
||||
}
|
||||
|
||||
let body = (eventContent['body'] || '').trim();
|
||||
const msgtype = eventContent['msgtype'];
|
||||
if (!body || !msgtype) return null; // invalid event, no preview
|
||||
|
||||
// XXX: Newer relations have a getRelation() function which is not compatible with replies.
|
||||
const mRelatesTo = event.getWireContent()['m.relates_to'];
|
||||
if (mRelatesTo && mRelatesTo['m.in_reply_to']) {
|
||||
// If this is a reply, get the real reply and use that
|
||||
body = (ReplyThread.stripPlainReply(body) || '').trim();
|
||||
if (!body) return null; // invalid event, no preview
|
||||
}
|
||||
|
||||
if (msgtype === 'm.emote') {
|
||||
return _t("%(senderName)s %(emote)s", {senderName: getSenderName(event), emote: body});
|
||||
}
|
||||
|
||||
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||
return body;
|
||||
} else {
|
||||
return _t("%(senderName)s: %(message)s", {senderName: getSenderName(event), message: body});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class NameEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (isSelf(event)) {
|
||||
return _t("You changed the room name");
|
||||
} else {
|
||||
return _t("%(senderName)s changed the room name", {senderName: getSenderName(event)});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class ReactionEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
const reaction = event.getRelation().key;
|
||||
if (!reaction) return;
|
||||
|
||||
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||
return reaction;
|
||||
} else {
|
||||
return _t("%(senderName)s: %(reaction)s", {senderName: getSenderName(event), reaction});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class StickerEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
const stickerName = event.getContent()['body'];
|
||||
if (!stickerName) return null;
|
||||
|
||||
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
||||
return stickerName;
|
||||
} else {
|
||||
return _t("%(senderName)s: %(stickerName)s", {senderName: getSenderName(event), stickerName});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { isValid3pidInvite } from "../../../RoomInvite";
|
||||
|
||||
export class ThirdPartyInviteEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (!isValid3pidInvite(event)) {
|
||||
const targetName = event.getPrevContent().display_name || _t("Someone");
|
||||
if (isSelf(event)) {
|
||||
return _t("You uninvited %(targetName)s", {targetName});
|
||||
} else {
|
||||
return _t("%(senderName)s uninvited %(targetName)s", {senderName: getSenderName(event), targetName});
|
||||
}
|
||||
} else {
|
||||
const targetName = event.getContent().display_name;
|
||||
if (isSelf(event)) {
|
||||
return _t("You invited %(targetName)s", {targetName});
|
||||
} else {
|
||||
return _t("%(senderName)s invited %(targetName)s", {senderName: getSenderName(event), targetName});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
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 { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { getSenderName, isSelf } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export class TopicEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
|
||||
if (isSelf(event)) {
|
||||
return _t("You changed the room topic");
|
||||
} else {
|
||||
return _t("%(senderName)s changed the room topic", {senderName: getSenderName(event)});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { DefaultTagID, TagID } from "../models";
|
||||
|
||||
export function isSelf(event: MatrixEvent): boolean {
|
||||
const selfUserId = MatrixClientPeg.get().getUserId();
|
||||
if (event.getType() === 'm.room.member') {
|
||||
return event.getStateKey() === selfUserId;
|
||||
}
|
||||
return event.getSender() === selfUserId;
|
||||
}
|
||||
|
||||
export function isSelfTarget(event: MatrixEvent): boolean {
|
||||
const selfUserId = MatrixClientPeg.get().getUserId();
|
||||
return event.getStateKey() === selfUserId;
|
||||
}
|
||||
|
||||
export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean {
|
||||
if (tagId !== DefaultTagID.DM) return true;
|
||||
|
||||
// We don't prefix anything in 1:1s
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) return true;
|
||||
return room.currentState.getJoinedMemberCount() !== 2;
|
||||
}
|
||||
|
||||
export function getSenderName(event: MatrixEvent): string {
|
||||
return event.sender ? event.sender.name : event.getSender();
|
||||
}
|
||||
|
||||
export function getTargetName(event: MatrixEvent): string {
|
||||
return event.target ? event.target.name : event.getStateKey();
|
||||
}
|
Loading…
Reference in New Issue