send channel type and lastMessage to client
use name of other user on DM channels add startdm api to start direct messages
This commit is contained in:
parent
fc09ffcb45
commit
cce2ad1f80
|
@ -584,7 +584,7 @@ export function fetchMe(): PromiseAction {
|
|||
function receiveChatHistory(
|
||||
cid: number,
|
||||
history: Array,
|
||||
) {
|
||||
): Action {
|
||||
return {
|
||||
type: 'RECEIVE_CHAT_HISTORY',
|
||||
cid,
|
||||
|
@ -748,6 +748,48 @@ export function setChatChannel(channelId: number): Action {
|
|||
};
|
||||
}
|
||||
|
||||
export function addChatChannel(channel: Array): Action {
|
||||
return {
|
||||
type: 'ADD_CHAT_CHANNEL',
|
||||
channel,
|
||||
};
|
||||
}
|
||||
|
||||
export function startDm(query): PromiseAction {
|
||||
return async (dispatch) => {
|
||||
const response = await fetch('api/startdm', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await response.json();
|
||||
if (res.errors) {
|
||||
dispatch(sweetAlert(
|
||||
'Direct Message Error',
|
||||
res.errors[0],
|
||||
'error',
|
||||
'OK',
|
||||
));
|
||||
}
|
||||
if (response.ok) {
|
||||
const { channel } = res;
|
||||
const channelId = channel[0];
|
||||
if (channelId) {
|
||||
await dispatch(addChatChannel(channel));
|
||||
dispatch(setChatChannel(channelId));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function hideModal(): Action {
|
||||
return {
|
||||
type: 'HIDE_MODAL',
|
||||
|
|
|
@ -70,6 +70,7 @@ export type Action =
|
|||
}
|
||||
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
|
||||
| { type: 'SET_CHAT_CHANNEL', channelId: number }
|
||||
| { type: 'ADD_CHAT_CHANNEL', channel: Array }
|
||||
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
||||
| { type: 'SET_CHAT_INPUT_MSG', message: string }
|
||||
| { type: 'ADD_CHAT_INPUT_MSG', message: string }
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
*/
|
||||
|
||||
import React, {
|
||||
useRef, useLayoutEffect, useState, useEffect,
|
||||
useRef, useState, useEffect, useCallback,
|
||||
} from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { MdChat } from 'react-icons/md';
|
||||
import { FaUserFriends } from 'react-icons/fa';
|
||||
|
||||
import type { State } from '../reducers';
|
||||
import {
|
||||
|
@ -20,57 +22,110 @@ const ChannelDropDown = ({
|
|||
setChannel,
|
||||
}) => {
|
||||
const [show, setShow] = useState(false);
|
||||
// 0: global and faction channels
|
||||
// 1: DMs
|
||||
const [type, setType] = useState(0);
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [chatChannelName, setChatChannelName] = useState('...');
|
||||
const wrapperRef = useRef(null);
|
||||
const buttonRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
setOffset(buttonRef.current.clientHeight);
|
||||
}, [buttonRef]);
|
||||
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
if (wrapperRef.current
|
||||
&& !wrapperRef.current.contains(event.target)
|
||||
&& !buttonRef.current.contains(event.target)
|
||||
) {
|
||||
setShow(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
return () => {
|
||||
} else {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
};
|
||||
}, [wrapperRef]);
|
||||
document.removeEventListener('touchstart', handleClickOutside);
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
for (let i = 0; i < channels.length; i += 1) {
|
||||
if (channels[i][0] === chatChannel) {
|
||||
setChatChannelName(channels[i][1]);
|
||||
}
|
||||
}
|
||||
}, [chatChannel, channels]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{position: 'relative'}}
|
||||
style={{ position: 'relative' }}
|
||||
>
|
||||
<div
|
||||
ref={buttonRef}
|
||||
style={{
|
||||
width: 50,
|
||||
}}
|
||||
ref={wrapperRef}
|
||||
onClick={() => setShow(true)}
|
||||
onClick={() => setShow(!show)}
|
||||
className="channelbtn"
|
||||
>
|
||||
{chatChannel}
|
||||
{chatChannelName}
|
||||
</div>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
bottom: offset + 5,
|
||||
right: 9,
|
||||
display: (show) ? 'initial' : 'none',
|
||||
}}
|
||||
className="channeldd"
|
||||
>
|
||||
{
|
||||
channels.map((ch) => (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => setType(0)}
|
||||
>
|
||||
<MdChat />
|
||||
</span>
|
||||
|
|
||||
<span
|
||||
onClick={() => setType(1)}
|
||||
>
|
||||
<FaUserFriends />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="channeldds"
|
||||
>
|
||||
{
|
||||
channels.filter((ch) => {
|
||||
const chType = ch[2];
|
||||
if (type === 1 && chType === 1) {
|
||||
return true;
|
||||
}
|
||||
if (type === 0 && chType !== 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}).map((ch) => (
|
||||
<div
|
||||
onClick={() => setChannel(ch[0])}
|
||||
style={(ch[0] === chatChannel) ? {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 17,
|
||||
} : null}
|
||||
>
|
||||
{ch[1]}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { connect } from 'react-redux';
|
|||
import {
|
||||
hideContextMenu,
|
||||
addToChatInputMessage,
|
||||
setChatInputMessage,
|
||||
startDm,
|
||||
} from '../actions';
|
||||
import type { State } from '../reducers';
|
||||
|
||||
|
@ -21,24 +21,24 @@ const UserContextMenu = ({
|
|||
yPos,
|
||||
uid,
|
||||
name,
|
||||
setInput,
|
||||
addToInput,
|
||||
dm,
|
||||
close,
|
||||
}) => {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
const handleClickOutside = (event) => {
|
||||
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
document.removeEventListener('touchstart', handleClickOutside);
|
||||
};
|
||||
}, [wrapperRef]);
|
||||
|
||||
|
@ -60,7 +60,8 @@ const UserContextMenu = ({
|
|||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setInput('loool');
|
||||
dm(uid);
|
||||
close();
|
||||
}}
|
||||
style={{ borderBottom: 'thin solid' }}
|
||||
>
|
||||
|
@ -108,8 +109,8 @@ function mapDispatchToProps(dispatch) {
|
|||
input.select();
|
||||
}
|
||||
},
|
||||
setInput(text) {
|
||||
dispatch(setChatInputMessage(text));
|
||||
dm(userId) {
|
||||
dispatch(startDm({ userId }));
|
||||
},
|
||||
close() {
|
||||
dispatch(hideContextMenu());
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* @flow */
|
||||
import Sequelize from 'sequelize';
|
||||
|
||||
import logger from './logger';
|
||||
import redis from '../data/redis';
|
||||
import User from '../data/models/User';
|
||||
|
@ -58,16 +56,16 @@ export class ChatProvider {
|
|||
const channel = await Channel.findOrCreate({
|
||||
attributes: [
|
||||
'id',
|
||||
'type',
|
||||
'lastMessage',
|
||||
],
|
||||
where: { name },
|
||||
defaults: {
|
||||
name,
|
||||
lastMessage: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
raw: true,
|
||||
});
|
||||
const { id } = channel[0];
|
||||
const { id, type, lastMessage } = channel[0];
|
||||
if (name === 'int') {
|
||||
this.intChannelId = id;
|
||||
}
|
||||
|
@ -77,6 +75,8 @@ export class ChatProvider {
|
|||
this.defaultChannels.push([
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
lastMessage,
|
||||
]);
|
||||
this.defaultChannelIds.push(id);
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ class Event {
|
|||
}
|
||||
|
||||
static broadcastChatMessage(message) {
|
||||
if (chatProvier.enChannelId && chatProvider.eventUserId) {
|
||||
if (chatProvider.enChannelId && chatProvider.eventUserId) {
|
||||
chatProvider.broadcastChatMessage(
|
||||
EVENT_USER_NAME,
|
||||
message,
|
||||
|
|
|
@ -30,12 +30,11 @@ export default async function getMe(user) {
|
|||
delete userdata.mailVerified;
|
||||
delete userdata.mcVerified;
|
||||
|
||||
userdata.canvases = canvases;
|
||||
userdata.channels = [
|
||||
...chatProvider.defaultChannels,
|
||||
...userdata.channels,
|
||||
];
|
||||
|
||||
userdata.canvases = canvases;
|
||||
|
||||
return userdata;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,23 @@ import { getIPFromRequest } from '../utils/ip';
|
|||
const include = [{
|
||||
model: Channel,
|
||||
as: 'channel',
|
||||
include: [{
|
||||
model: RegUser,
|
||||
as: 'dmu1',
|
||||
foreignKey: 'dmu1id',
|
||||
attributes: [
|
||||
'id',
|
||||
'name',
|
||||
],
|
||||
}, {
|
||||
model: RegUser,
|
||||
as: 'dmu2',
|
||||
foreignKey: 'dmu2id',
|
||||
attributes: [
|
||||
'id',
|
||||
'name',
|
||||
],
|
||||
}],
|
||||
}, {
|
||||
model: RegUser,
|
||||
through: UserBlock,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import DataType from 'sequelize';
|
||||
|
||||
import Model from '../sequelize';
|
||||
import RegUser from './RegUser';
|
||||
|
||||
|
@ -37,6 +38,7 @@ const Channel = Model.define('Channel', {
|
|||
|
||||
lastMessage: {
|
||||
type: DataType.DATE,
|
||||
defaultValue: DataType.literal('CURRENT_TIMESTAMP'),
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
|
@ -49,6 +51,7 @@ const Channel = Model.define('Channel', {
|
|||
* (associating it here allows us too
|
||||
* keep track of users leaving and joining DMs and ending up
|
||||
* in the same conversation)
|
||||
* dmu1id < dmu2id
|
||||
*/
|
||||
Channel.belongsTo(RegUser, {
|
||||
as: 'dmu1',
|
||||
|
|
|
@ -14,7 +14,6 @@ import logger from '../../core/logger';
|
|||
import Model from '../sequelize';
|
||||
import RegUser from './RegUser';
|
||||
import { getIPv6Subnet } from '../../utils/ip';
|
||||
|
||||
import { ADMIN_IDS } from '../../core/config';
|
||||
|
||||
|
||||
|
@ -35,7 +34,6 @@ class User {
|
|||
this.wait = null;
|
||||
// following gets populated by passport
|
||||
this.regUser = null;
|
||||
this.channelIds = [];
|
||||
}
|
||||
|
||||
static async name2Id(name: string) {
|
||||
|
@ -56,14 +54,30 @@ class User {
|
|||
setRegUser(reguser) {
|
||||
this.regUser = reguser;
|
||||
this.id = reguser.id;
|
||||
if (reguser.channel) {
|
||||
for (let i = 0; i < reguser.channel.length; i += 1) {
|
||||
this.channelIds.push(reguser.channel[i].id);
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
lastMessage,
|
||||
dmu1,
|
||||
dmu2,
|
||||
} = reguser.channel[i];
|
||||
// in DMs the name is the name of the other user
|
||||
let { name } = reguser.channel[i];
|
||||
if (type === 1) {
|
||||
name = (dmu1.id === this.id) ? dmu2.name : dmu1.name;
|
||||
}
|
||||
this.channelIds.push(id);
|
||||
this.channels.push([
|
||||
reguser.channel[i].id,
|
||||
reguser.channel[i].name,
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
lastMessage,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getName() {
|
||||
return (this.regUser) ? this.regUser.name : null;
|
||||
|
|
|
@ -14,4 +14,16 @@ const UserBlock = Model.define('UserBlock', {
|
|||
timestamps: false,
|
||||
});
|
||||
|
||||
export async function isUserBlockedBy(userId, blockedById) {
|
||||
const exists = await UserBlock.findOne({
|
||||
where: {
|
||||
uid: userId,
|
||||
buid: blockedById,
|
||||
},
|
||||
raw: true,
|
||||
attributes: ['uid'],
|
||||
});
|
||||
return !!exists;
|
||||
}
|
||||
|
||||
export default UserBlock;
|
||||
|
|
|
@ -33,6 +33,18 @@ export default function chat(
|
|||
};
|
||||
}
|
||||
|
||||
case 'ADD_CHAT_CHANNEL': {
|
||||
const { channel } = action;
|
||||
const channelId = channel[0];
|
||||
const channels = state.channels
|
||||
.filter((ch) => (ch[0] !== channelId));
|
||||
channels.push(channel);
|
||||
return {
|
||||
...state,
|
||||
channels,
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET_CHAT_FETCHING': {
|
||||
const { fetching } = action;
|
||||
return {
|
||||
|
|
|
@ -18,6 +18,7 @@ import auth from './auth';
|
|||
import ranking from './ranking';
|
||||
import history from './history';
|
||||
import chatHistory from './chathistory';
|
||||
import startDm from './startdm';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
@ -80,6 +81,8 @@ router.post('/mctp', mctp);
|
|||
|
||||
router.get('/chathistory', chatHistory);
|
||||
|
||||
router.post('/startdm', startDm);
|
||||
|
||||
router.use('/auth', auth(passport));
|
||||
|
||||
export default router;
|
||||
|
|
131
src/routes/api/startdm.js
Normal file
131
src/routes/api/startdm.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
*
|
||||
* starts a DM session
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import logger from '../../core/logger';
|
||||
import { Channel, UserChannel, RegUser } from '../../data/models';
|
||||
import { isUserBlockedBy } from '../../data/models/UserBlock';
|
||||
|
||||
async function startDm(req: Request, res: Response) {
|
||||
let userId = parseInt(req.body.userId, 10);
|
||||
let { userName } = req.body;
|
||||
const { user } = req;
|
||||
|
||||
const errors = [];
|
||||
const query = {};
|
||||
if (userId) {
|
||||
if (userId && Number.isNaN(userId)) {
|
||||
errors.push('Invalid userId');
|
||||
}
|
||||
query.id = userId;
|
||||
}
|
||||
if (userName) {
|
||||
query.name = userName;
|
||||
}
|
||||
if (!userName && !userId) {
|
||||
errors.push('No userId or userName defined');
|
||||
}
|
||||
if (!user || !user.regUser) {
|
||||
errors.push('You are not logged in');
|
||||
}
|
||||
if (user && userId && user.id === userId) {
|
||||
errors.push('You can not start DM to yourself.');
|
||||
}
|
||||
if (errors.length) {
|
||||
res.status(400);
|
||||
res.json({
|
||||
errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const targetUser = await RegUser.findOne({
|
||||
where: query,
|
||||
attributes: [
|
||||
'id',
|
||||
'name',
|
||||
],
|
||||
raw: true,
|
||||
});
|
||||
if (!targetUser) {
|
||||
res.status(401);
|
||||
res.json({
|
||||
errors: ['Target user does not exist'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
userId = targetUser.id;
|
||||
userName = targetUser.name;
|
||||
|
||||
/*
|
||||
* check if blocked
|
||||
*/
|
||||
if (await isUserBlockedBy(user.id, userId)) {
|
||||
res.status(401);
|
||||
res.json({
|
||||
errors: ['You are blocked by this user'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Creating DM Channel between ${user.regUser.name} and ${userName}`,
|
||||
);
|
||||
/*
|
||||
* start DM session
|
||||
*/
|
||||
let dmu1id = null;
|
||||
let dmu2id = null;
|
||||
if (user.id > userId) {
|
||||
dmu1id = userId;
|
||||
dmu2id = user.id;
|
||||
} else {
|
||||
dmu1id = user.id;
|
||||
dmu2id = userId;
|
||||
}
|
||||
|
||||
const channel = await Channel.findOrCreate({
|
||||
where: {
|
||||
type: 1,
|
||||
dmu1id,
|
||||
dmu2id,
|
||||
},
|
||||
raw: true,
|
||||
});
|
||||
const ChannelId = channel[0].id;
|
||||
const { lastMessage } = channel[0];
|
||||
|
||||
const promises = [
|
||||
UserChannel.findOrCreate({
|
||||
where: {
|
||||
UserId: dmu1id,
|
||||
ChannelId,
|
||||
},
|
||||
raw: true,
|
||||
}),
|
||||
UserChannel.findOrCreate({
|
||||
where: {
|
||||
UserId: dmu2id,
|
||||
ChannelId,
|
||||
},
|
||||
raw: true,
|
||||
}),
|
||||
];
|
||||
await Promise.all(promises);
|
||||
|
||||
res.json({
|
||||
channel: [
|
||||
ChannelId,
|
||||
userName,
|
||||
1,
|
||||
lastMessage,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export default startDm;
|
|
@ -167,6 +167,13 @@ tr:nth-child(even) {
|
|||
|
||||
.channeldd {
|
||||
background-color: rgba(226, 226, 226);
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.channeldds {
|
||||
height: 120px;
|
||||
overflow-y: scroll;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.actionbuttons, .coorbox, .onlinebox, .cooldownbox, #palettebox {
|
||||
|
|
Loading…
Reference in New Issue
Block a user