You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
5.3 KiB
JavaScript

"use strict";
const Promise = require("bluebird");
const React = require("react");
const create = require("create-react-class");
const sanitize = require("sanitize-html");
const { expression } = require("dataprog");
const Event = require("./events/Event.js");
const Info = require("./info.js");
const Input = require("./input.js");
const User = require("./events/user.js");
const Loading = require("./loading.js");
const generateJdenticon = require("../lib/generate-jdenticon");
const generateThumbnailUrl = require("../lib/generate-thumbnail-url");
const groupEvents = require("../lib/group-events");
const parseMXC = require("../lib/parse-mxc");
const withElement = require("../lib/with-element");
let eventFunctions = {
plaintext: function() {
if (this.type == "m.room.message") {
if (this.content.format == "org.matrix.custom.html") {
return sanitize(this.content.formatted_body, {allowedTags: []});
} else {
return this.content.body;
}
} else if (this.type == "m.room.member") {
if (this.content.membership == "invite") {
return `${this.sender} invited ${this.state_key}`;
} else if (this.content.membership == "join") {
return `${this.state_key} joined the room`;
} else if (this.content.membership == "leave") {
return `${this.state_key} left the room`;
} else if (this.content.membership == "kick") {
return `${this.sender} kicked ${this.state_key}`;
} else if (this.content.membership == "ban") {
return `${this.sender} banned ${this.state_key}`;
} else {
return "unknown member event";
}
} else if (this.type == "m.room.avatar") {
if (this.content.url.length > 0) {
return `${this.sender} changed the room avatar`;
}
} else if (this.type == "m.room.name") {
return `${this.sender} changed the room name to ${this.content.name}`;
} else {
return "unknown event";
}
}
};
let chat = create({
displayName: "Chat",
getInitialState: function() {
return {
ref: null,
loading: false
};
},
getSnapshotBeforeUpdate: function(_oldProps, _oldState) {
let ref = this.state.ref;
if (ref != null && (ref.scrollHeight - ref.offsetHeight) - ref.scrollTop < 100) {
// Less than 100px from bottom
return true;
} else {
return null;
}
},
componentDidUpdate(prevProps, prevState, snapshot) {
let ref = this.state.ref;
if (ref != null && snapshot) {
// scroll to bottom
ref.scrollTop = (ref.scrollHeight - ref.offsetHeight);
}
},
setRef: function(ref) {
if (ref != null) {
this.setState({ ref: ref });
}
},
onReplyClick: function(e) {
this.setState({ replyEvent: e });
},
paginateBackwards: function() {
if (!this.state.loading) {
let client = this.props.client;
let timeline = client.getRoom(this.props.roomId).getLiveTimeline();
this.setState({loading: true});
return Promise.try(() => {
return client.paginateEventTimeline(timeline, {backwards: true});
}).then(() => {
this.setState({loading: false});
});
}
},
render: function() {
let client = this.props.client;
let empty = <div className="main" />;
if (this.props.roomId == null) {
//empty screen
return empty;
} else {
let room = client.getRoom(this.props.roomId);
if (room == null) {
return empty;
} else {
let liveTimeline = room.getLiveTimeline();
let liveTimelineEvents = liveTimeline.getEvents();
let events = liveTimelineEvents.map((item) => {
let event = item.event;
return Object.assign(
event,
eventFunctions,
(event.sender == null)
/* Whether this event is a local echo */
? { local: true, sender: event.user_id }
: null
);
});
let eventGroups = groupEvents(events);
//TODO: replace with something that only renders events in view
return (
<div className="main">
<Info room={room} />
<div className="chat" ref={this.setRef}>
<div className="events">
<div className="paginateBackwards" onClick={this.paginateBackwards}>
{this.state.loading ?
<Loading/> :
<span>load older messages</span>
}
</div>
{(eventGroups.map((group) => {
return <EventGroup key={`${this.props.roomId}-${group.events[0].event_id}`} events={group.events} client={this.props.client} room={room} onReplyClick={this.onReplyClick}/>;
}))}
</div>
</div>
<Input client={client} roomId={this.props.roomId} replyEvent={this.state.replyEvent} onReplyClick={this.onReplyClick}/>
</div>
);
}
}
}
});
function EventGroup({ events, room, client, onReplyClick }) {
let setAvatarRef = withElement((element) => {
generateJdenticon(user.userId).update(element);
});
let user = client.getUser(events[0].sender);
let avatar = expression(() => {
if (user.avatarUrl != null) {
let url = generateThumbnailUrl({
homeserver: client.baseUrl,
mxc: parseMXC(user.avatarUrl),
width: 128,
height: 128
});
return <img id="avatar" src={url} />;
} else {
return <svg id="avatar" ref={setAvatarRef} />;
}
});
return (
<div className="eventGroup">
{avatar}
<div className="col">
<User user={user}/>
{events.map((event, key) => {
return <Event event={event} key={key} client={client} room={room} onReplyClick={onReplyClick}/>;
})}
</div>
</div>
);
}
module.exports = chat;