messagepad (react is great!)

This commit is contained in:
undefined committed 2020-06-26 15:51:42 +08:00
1 parent 39ea30664d
commit 560e920624
13 files changed
+146 -279

No files matched your search

+45 -79
View File
@@ -1,3 +1,4 @@
const fs = require('fs');
const { const {
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError, VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
NotFoundError, NotFoundError,
@@ -13,6 +14,7 @@ const message = require('../model/message');
const document = require('../model/document'); const document = require('../model/document');
const system = require('../model/system'); const system = require('../model/system');
const user = require('../model/user'); const user = require('../model/user');
const file = require('../model/file');
const setting = require('../model/setting'); const setting = require('../model/setting');
const domain = require('../model/domain'); const domain = require('../model/domain');
const discussion = require('../model/discussion'); const discussion = require('../model/discussion');
@@ -140,13 +142,13 @@ class HomeSecurityHandler extends Handler {
this.user.checkPassword(currentPassword); this.user.checkPassword(currentPassword);
const udoc = await user.getByMail(mail); const udoc = await user.getByMail(mail);
if (udoc) throw new UserAlreadyExistError(mail); if (udoc) throw new UserAlreadyExistError(mail);
const [rid] = await token.add( const [code] = await token.add(
token.TYPE_CHANGEMAIL, token.TYPE_CHANGEMAIL,
await system.get('changemail_token_expire_seconds'), await system.get('changemail_token_expire_seconds'),
{ uid: this.udoc._id, mail }, { uid: this.udoc._id, mail },
); );
await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', { await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', {
url: `/changeMail/${rid}`, uname: this.udoc.uname, url: this.url('user_changemail_with_code', { code }), uname: this.udoc.uname,
}); });
this.response.template = 'user_changemail_mail_sent.html'; this.response.template = 'user_changemail_mail_sent.html';
} }
@@ -165,7 +167,7 @@ class HomeSecurityHandler extends Handler {
async postDeleteAllTokens() { async postDeleteAllTokens() {
await token.delByUid(this.user._id); await token.delByUid(this.user._id);
this.response.redirect = '/login'; this.response.redirect = this.url('user_login');
} }
} }
@@ -219,73 +221,47 @@ class UserChangemailWithCodeHandler extends Handler {
user.setEmail(this.user._id, tdoc.mail), user.setEmail(this.user._id, tdoc.mail),
token.del(code, token.TYPE_CHANGEMAIL), token.del(code, token.TYPE_CHANGEMAIL),
]); ]);
this.response.redirect = '/home/security'; this.response.redirect = this.url('home_security');
} }
} }
class HomeMessagesHandler extends Handler { class HomeMessagesHandler extends Handler {
udoc(udict, key) { // eslint-disable-line class-methods-use-this
const udoc = udict[key];
if (!udoc) return;
const gravatar_url = misc.gravatar(udoc.gravatar);
if (udoc.gravatar) udict[key] = { ...udoc, gravatar_url, gravatar: '' };
}
async prepare() {
this.checkPerm(PERM_LOGGEDIN);
}
async get() { async get() {
// TODO(iceboy): projection, pagination. // TODO(iceboy): projection, pagination.
const messages = await message.getByUser(this.user._id); const messages = await message.getByUser(this.user._id);
const udict = await user.getList([ const udict = await user.getList('system', [
...messages.map((mdoc) => mdoc.from), ...messages.map((mdoc) => mdoc.from),
...messages.map((mdoc) => mdoc.to), ...messages.map((mdoc) => mdoc.to),
]); ]);
// TODO(twd2): improve here: // TODO(twd2): improve here:
const parsed = {}; const parsed = {};
for (const m of messages) { for (const m of messages) {
if (m.from === this.user._id) { const target = m.from === this.user._id ? m.to : m.from;
if (!parsed[m.to]) { if (!parsed[target]) {
parsed[m.to] = { parsed[target] = {
udoc: { ...udict[m.to], gravatar: misc.gravatar(udict[m.to].gravatar) }, _id: target,
messages: [], udoc: { ...udict[target], gravatar: misc.gravatar(udict[target].gravatar) },
}; messages: [],
} };
parsed[m.to].messages.push(m);
} else {
if (!parsed[m.from]) {
parsed[m.from] = {
udoc: { ...udict[m.from], gravatar: misc.gravatar(udict[m.from].gravatar) },
messages: [],
};
}
parsed[m.from].messages.push(m);
} }
parsed[target].messages.push(m);
} }
const path = [ const path = [
['Hydro', 'homepage'], ['Hydro', 'homepage'],
['home_messages', null], ['home_messages', null],
]; ];
this.response.body = { messages, udict, path }; this.response.body = { messages: parsed, path };
this.response.template = 'home_messages.html'; this.response.template = 'home_messages.html';
} }
async postSend({ uid, content, type = 'full' }) { async postSend({ uid, content }) {
const udoc = await user.getById('system', uid); const udoc = await user.getById('system', uid);
let mdoc = await message.send(this.user._id, uid, content); const mdoc = await message.send(this.user._id, uid, content);
if (type === 'single') {
mdoc = mdoc.reply[mdoc.reply.length - 1];
}
// TODO(twd2): improve here: projection // TODO(twd2): improve here: projection
mdoc.from_udoc = this.user;
mdoc.to_udoc = udoc;
this.udoc(mdoc, 'from');
this.udoc(mdoc, 'to');
if (this.user._id !== uid) { if (this.user._id !== uid) {
await bus.publish(`user_message-${uid}`, { type: 'new', data: mdoc }); await bus.publish(`user_message-${uid}`, { mdoc, udoc });
} }
this.back(type === 'full' ? { mdoc } : { reply: mdoc }); this.back({ mdoc, udoc });
} }
async postDeleteMessage({ messageId }) { async postDeleteMessage({ messageId }) {
@@ -296,53 +272,43 @@ class HomeMessagesHandler extends Handler {
class HomeMessagesConnectionHandler extends ConnectionHandler { class HomeMessagesConnectionHandler extends ConnectionHandler {
async prepare() { async prepare() {
bus.subscribe([`message_received-${this.user._id}`], this.onMessageReceived); bus.subscribe([`user_message-${this.user._id}`], (...args) => this.onMessageReceived(...args));
} }
async onMessageReceived(e) { async onMessageReceived(e) {
this.send(...e.value); this.send(e.value);
} }
async clearup() { async cleanup() {
bus.unsubscribe(this.onMessageReceived); bus.unsubscribe(this.onMessageReceived);
} }
} }
// TODO draft
class HomeFileHandler extends Handler {
async get() {
const ufdocs = await file.getMulti({ owner: this.user._id }).toArray();
const fdict = await fs.getMetaDict(ufdocs.map((ufdoc) => ufdoc._id));
this.response.template = 'home_file.html';
this.response.body = { ufdocs, fdict };
}
async postDelete(ufid) {
const ufdoc = await file.get(ufid);
if (ufdoc.owner !== this.user._id) this.checkPriv(PRIV_DELETE_FILE);
const result = await file.del(ufdoc._id);
if (result) await file.decUsage(this.user._id, ufdoc.length);
this.back();
}
}
async function apply() { async function apply() {
Route('homepage', '/', HomeHandler); Route('homepage', '/', HomeHandler);
Route('home_security', '/home/security', HomeSecurityHandler); Route('home_security', '/home/security', HomeSecurityHandler, PERM_LOGGEDIN);
Route('user_changemail_with_code', '/home/changeMail/:code', UserChangemailWithCodeHandler); Route('user_changemail_with_code', '/home/changeMail/:code', UserChangemailWithCodeHandler, PERM_LOGGEDIN);
Route('home_settings', '/home/settings/:category', HomeSettingsHandler); Route('home_settings', '/home/settings/:category', HomeSettingsHandler, PERM_LOGGEDIN);
Route('home_messages', '/home/messages', HomeMessagesHandler); Route('home_messages', '/home/messages', HomeMessagesHandler, PERM_LOGGEDIN);
Connection('home_messages_conn', '/home/messages-conn', HomeMessagesConnectionHandler); Connection('home_messages_conn', '/home/messages-conn', HomeMessagesConnectionHandler);
} }
global.Hydro.handler.home = module.exports = apply; global.Hydro.handler.home = module.exports = apply;
/*
@app.route('/home/file', 'home_file', global_route=True)
class HomeFileHandler(base.OperationHandler):
def file_url(this, fdoc):
return options.cdn_prefix.rstrip('/') + \
this.reverse_url('fs_get', domain_id=builtin.DOMAIN_ID_SYSTEM,
secret=fdoc['metadata']['secret'])
@base.require_priv(builtin.PRIV_USER_PROFILE)
async def get(this):
ufdocs = await userfile.get_multi(owner_uid=this.user['_id']).to_list()
fdict = await fs.get_meta_dict(ufdoc.get('file_id') for ufdoc in ufdocs)
this.render('home_file.html', ufdocs=ufdocs, fdict=fdict)
@base.require_priv(builtin.PRIV_USER_PROFILE)
@base.post_argument
@base.require_csrfToken
@base.sanitize
async def post_delete(this, *, ufid: document.convert_doc_id):
ufdoc = await userfile.get(ufid)
if not this.own(ufdoc, priv=builtin.PRIV_DELETE_FILE_this):
this.check_priv(builtin.PRIV_DELETE_FILE)
result = await userfile.delete(ufdoc['doc_id'])
if result:
await userfile.dec_usage(this.user['_id'], ufdoc['length'])
this.redirect(this.referer_or_main)
*/
+11 -4
View File
@@ -3,6 +3,8 @@ const moment = require('moment-timezone');
const { Route, Handler } = require('../service/server.js'); const { Route, Handler } = require('../service/server.js');
const user = require('../model/user'); const user = require('../model/user');
const token = require('../model/token'); const token = require('../model/token');
const record = require('../model/record');
const problem = require('../model/problem');
const task = require('../model/task'); const task = require('../model/task');
const system = require('../model/system'); const system = require('../model/system');
const { sendMail } = require('../lib/mail'); const { sendMail } = require('../lib/mail');
@@ -149,8 +151,14 @@ class UserDetailHandler extends Handler {
const isSelfProfile = this.user._id === uid; const isSelfProfile = this.user._id === uid;
const udoc = await user.getById(domainId, uid, true); const udoc = await user.getById(domainId, uid, true);
const sdoc = await token.getMostRecentSessionByUid(uid); const sdoc = await token.getMostRecentSessionByUid(uid);
const rdocs = await record.getByUid(domainId, uid);
const pdict = await problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false);
// Remove sensitive data
if (!isSelfProfile) sdoc.createIp = sdoc.updateIp = sdoc._id = '';
this.response.template = 'user_detail.html'; this.response.template = 'user_detail.html';
this.response.body = { isSelfProfile, udoc, sdoc }; this.response.body = {
isSelfProfile, udoc, sdoc, rdocs, pdict,
};
} }
} }
@@ -177,7 +185,7 @@ class UserSearchHandler extends Handler {
if (udoc) udocs.push(udoc); if (udoc) udocs.push(udoc);
for (const i in udocs) { for (const i in udocs) {
if (udocs[i].gravatar) { if (udocs[i].gravatar) {
udocs[i].gravatar_url = misc.gravatar(udocs[i].gravatar); udocs[i].gravatar = misc.gravatar(udocs[i].gravatar);
} }
} }
this.response.body = udocs; this.response.body = udocs;
@@ -248,10 +256,9 @@ class OauthCallbackHandler extends Handler {
} }
async google({ async google({
code, scope, authuser, prompt, error, state, code, error, state,
}) { }) {
if (error) throw new UserFacingError(error); if (error) throw new UserFacingError(error);
console.log(scope, authuser, prompt);
const [ const [
[appid, secret, url, proxy], [appid, secret, url, proxy],
s, s,
+5 -2
View File
@@ -4,9 +4,12 @@ const db = require('../service/db.js');
const coll = db.collection('message'); const coll = db.collection('message');
async function send(from, to, content) { async function send(from, to, content) {
await coll.insertOne({ const res = await coll.insertOne({
from, to, content, unread: true, from, to, content, unread: true,
}); });
return {
from, to, content, unread: true, _id: res.insertedId,
};
} }
async function get(_id) { async function get(_id) {
@@ -16,7 +19,7 @@ async function get(_id) {
} }
function getByUser(uid) { function getByUser(uid) {
return coll.find({ $or: [{ from: uid }, { to: uid }] }).toArray(); return coll.find({ $or: [{ from: uid }, { to: uid }] }).sort('_id', 1).toArray();
} }
function getMany(query, sort, page, limit) { function getMany(query, sort, page, limit) {
+5 -1
View File
@@ -13,6 +13,7 @@ const sockjs = require('sockjs');
const http = require('http'); const http = require('http');
const validator = require('../lib/validator'); const validator = require('../lib/validator');
const template = require('../lib/template'); const template = require('../lib/template');
const misc = require('../lib/misc');
const user = require('../model/user'); const user = require('../model/user');
const system = require('../model/system'); const system = require('../model/system');
const blacklist = require('../model/blacklist'); const blacklist = require('../model/blacklist');
@@ -157,6 +158,8 @@ class Handler {
async renderHTML(name, context) { async renderHTML(name, context) {
if (enableLog) console.time(name); if (enableLog) console.time(name);
this.hasPerm = (perm) => this.user.hasPerm(perm); this.hasPerm = (perm) => this.user.hasPerm(perm);
// FIXME fix this ugly hack
this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
const res = await template.render(name, Object.assign(context, { const res = await template.render(name, Object.assign(context, {
handler: this, handler: this,
url: (...args) => this.url(...args), url: (...args) => this.url(...args),
@@ -359,6 +362,7 @@ async function handle(ctx, HandlerClass, permission) {
const args = { const args = {
domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body, domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body,
}; };
h.args = args;
if (h.___prepare) await h.___prepare(args); if (h.___prepare) await h.___prepare(args);
if (permission) h.checkPerm(permission); if (permission) h.checkPerm(permission);
@@ -375,7 +379,6 @@ async function handle(ctx, HandlerClass, permission) {
if (e instanceof ValidationError) throw e; if (e instanceof ValidationError) throw e;
throw new ValidationError(`Argument ${checking} check failed`); throw new ValidationError(`Argument ${checking} check failed`);
} }
h.args = args;
if (h.__prepare) await h.__prepare(args); if (h.__prepare) await h.__prepare(args);
if (h._prepare) await h._prepare(args); if (h._prepare) await h._prepare(args);
if (h.prepare) await h.prepare(args); if (h.prepare) await h.prepare(args);
@@ -440,6 +443,7 @@ class ConnectionHandler {
async renderHTML(name, context) { async renderHTML(name, context) {
this.hasPerm = (perm) => this.user.hasPerm(perm); this.hasPerm = (perm) => this.user.hasPerm(perm);
this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
const res = await template.render(name, Object.assign(context, { const res = await template.render(name, Object.assign(context, {
handler: this, handler: this,
url: (...args) => this.url(...args), url: (...args) => this.url(...args),
+2 -2
View File
@@ -23,13 +23,13 @@
<meta name="theme-color" content="#56758f"> <meta name="theme-color" content="#56758f">
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}"> <link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
<title>{{ handler.renderTitle(page_title) }}</title> <title>{{ handler.renderTitle(page_name) }}</title>
{% if not isIE(handler.request.headers['user-agent']) %} {% if not isIE(handler.request.headers['user-agent']) %}
<script> <script>
var _htmlNode = document.documentElement; var _htmlNode = document.documentElement;
_htmlNode.className = _htmlNode.className.replace(' nojs', ' hasjs'); _htmlNode.className = _htmlNode.className.replace(' nojs', ' hasjs');
var UiContext = {{ handler.UIContext|json|safe }}; var UiContext = {{ handler.UIContext|json|safe }};
var UserContext = {{ handler.user|json|safe }}; var UserContext = {{ handler._user|json|safe }};
</script> </script>
{% endif %} {% endif %}
</head> </head>
@@ -7,6 +7,7 @@ import 'jquery-scroll-lock';
import 'jquery.easing'; import 'jquery.easing';
import i18n from 'vj/utils/i18n'; import i18n from 'vj/utils/i18n';
import { parse as parseMongoId } from 'vj/utils/mongoId';
import Message from './MessageComponent'; import Message from './MessageComponent';
const mapStateToProps = (state) => ({ const mapStateToProps = (state) => ({
@@ -22,24 +23,17 @@ export default class MessagePadDialogueContentContainer extends React.PureCompon
$(this.refs.list).scrollLock({ strict: true }); $(this.refs.list).scrollLock({ strict: true });
} }
UNSAFE_componentWillUpdate(nextProps) { // eslint-disable-line camelcase componentDidUpdate(prevProps) {
if (nextProps.activeId !== this.props.activeId) { const node = this.refs.list;
if (this.props.activeId !== prevProps.activeId) {
this.scrollToBottom = true; this.scrollToBottom = true;
this.scrollWithAnimation = false; this.scrollWithAnimation = false;
return; } else if (node.scrollTop + node.offsetHeight === node.scrollHeight) {
}
const node = this.refs.list;
if (node.scrollTop + node.offsetHeight === node.scrollHeight) {
this.scrollToBottom = true; this.scrollToBottom = true;
this.scrollWithAnimation = true; this.scrollWithAnimation = true;
return; } else this.scrollToBottom = false;
}
this.scrollToBottom = false;
}
componentDidUpdate() {
if (this.scrollToBottom) { if (this.scrollToBottom) {
const node = this.refs.list;
const targetScrollTop = node.scrollHeight - node.offsetHeight; const targetScrollTop = node.scrollHeight - node.offsetHeight;
if (this.scrollWithAnimation) { if (this.scrollWithAnimation) {
$(node).stop().animate({ scrollTop: targetScrollTop }, 200, 'easeOutCubic'); $(node).stop().animate({ scrollTop: targetScrollTop }, 200, 'easeOutCubic');
@@ -53,19 +47,19 @@ export default class MessagePadDialogueContentContainer extends React.PureCompon
if (this.props.activeId === null) { if (this.props.activeId === null) {
return []; return [];
} }
return _.map(this.props.item.reply, (reply, idx) => ( return _.map(this.props.item.messages, (reply) => (
<Message <Message
key={idx} key={reply._id}
isSelf={reply.from === UserContext._id} isSelf={reply.from === UserContext._id}
faceUrl={ faceUrl={
reply.from === this.props.item.from reply.from === UserContext._id
? this.props.item.from_udoc.gravatar ? UserContext.gravatar
: this.props.item.to_udoc.gravatar : this.props.item.udoc.gravatar
} }
> >
<div>{reply.content}</div> <div>{reply.content}</div>
<time data-tooltip={moment(reply.at).format('YYYY-MM-DD HH:mm:ss')}> <time data-tooltip={moment(parseMongoId(reply._id).timestamp * 1000).format('YYYY-MM-DD HH:mm:ss')}>
<TimeAgo datetime={reply.at} locale={i18n('timeago_locale')} /> <TimeAgo datetime={parseMongoId(reply._id).timestamp * 1000} locale={i18n('timeago_locale')} />
</time> </time>
</Message> </Message>
)); ));
@@ -28,9 +28,9 @@ export default class MessagePadDialogueListContainer extends React.PureComponent
render() { render() {
const orderedDialogues = _.orderBy( const orderedDialogues = _.orderBy(
_.values(this.props.dialogues), _.values(this.props.dialogues),
(dialogue) => (dialogue.isPlaceholder (dialogue) => (_.maxBy(dialogue.messages, '_id')
? Number.POSITIVE_INFINITY ? _.maxBy(dialogue.messages, '_id')._id
: _.maxBy(dialogue.reply, 'at').at), : Number.POSITIVE_INFINITY),
'desc', 'desc',
); );
return ( return (
@@ -38,21 +38,9 @@ export default class MessagePadDialogueListContainer extends React.PureComponent
{_.map(orderedDialogues, (dialogue) => ( {_.map(orderedDialogues, (dialogue) => (
<ListItem <ListItem
key={dialogue._id} key={dialogue._id}
userName={ userName={dialogue.udoc.uname}
dialogue.from === UserContext._id summary={(_.last(dialogue.messages)?.content) || ''}
? dialogue.to_udoc.uname faceUrl={dialogue.udoc.gravatar}
: dialogue.from_udoc.uname
}
summary={
dialogue.isPlaceholder
? ''
: _.last(dialogue.reply).content
}
faceUrl={
dialogue.from === UserContext._id
? dialogue.to_udoc.gravatar
: dialogue.from_udoc.gravatar
}
active={dialogue._id === this.props.activeId} active={dialogue._id === this.props.activeId}
onClick={() => this.props.handleClick(dialogue._id)} onClick={() => this.props.handleClick(dialogue._id)}
/> />
@@ -27,8 +27,7 @@ const mapDispatchToProps = (dispatch) => ({
}, },
}); });
}, },
postSend(placeholderId, uid, value) { postSend(uid, value) {
if (placeholderId === null) return;
const req = request.post('', { const req = request.post('', {
operation: 'send', operation: 'send',
uid, uid,
@@ -38,23 +37,7 @@ const mapDispatchToProps = (dispatch) => ({
type: 'DIALOGUES_POST_SEND', type: 'DIALOGUES_POST_SEND',
payload: req, payload: req,
meta: { meta: {
placeholderId, dialogueId: uid,
},
});
},
postReply(_id, uid, value) {
if (_id === null) return;
const req = request.post('', {
operation: 'send',
type: 'single',
uid,
content: value,
});
dispatch({
type: 'DIALOGUES_POST_REPLY',
payload: req,
meta: {
dialogueId: _id,
}, },
}); });
}, },
@@ -88,20 +71,10 @@ export default class MessagePadInputContainer extends React.PureComponent {
} }
submit() { submit() {
const state = this.context.store.getState(); this.props.postSend(
if (state.dialogues[this.props.activeId].isPlaceholder) { this.props.activeId,
this.props.postSend( this.props.inputValue,
this.props.activeId, );
state.dialogues[this.props.activeId].to,
this.props.inputValue,
);
} else {
this.props.postReply(
this.props.activeId,
state.dialogues[this.props.activeId].to,
this.props.inputValue,
);
}
} }
render() { render() {
@@ -26,7 +26,7 @@ export default function reducer(state = null, action) {
return action.payload; return action.payload;
} }
case 'DIALOGUES_POST_SEND_FULFILLED': { case 'DIALOGUES_POST_SEND_FULFILLED': {
return action.payload.mdoc._id; return action.meta.dialogueId;
} }
default: default:
return state; return state;
+30 -61
View File
@@ -3,79 +3,48 @@ import _ from 'lodash';
export default function reducer(state = {}, action) { export default function reducer(state = {}, action) {
switch (action.type) { switch (action.type) {
case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': { case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': {
const { messages, udict } = action.payload; const { messages } = action.payload;
return _.keyBy( return _.keyBy(messages, 'udoc._id');
_.filter(
_.map(messages, (m) => ({
to_udoc: udict[m.to],
from_udoc: udict[m.from],
...m,
})),
(m) => m.to_udoc && m.from_udoc,
),
'_id',
);
} }
case 'DIALOGUES_CREATE': { case 'DIALOGUES_CREATE': {
const { id, user } = action.payload; const { user } = action.payload;
return { return {
...state, ...state,
[id]: { [user._id]: {
_id: id, _id: user._id,
from: UserContext._id, udoc: { ...user },
to: user._id, messages: [],
to_udoc: { ...user },
reply: [],
isPlaceholder: true,
},
};
}
case 'DIALOGUES_POST_REPLY_FULFILLED': {
const id = action.meta.dialogueId;
const { reply } = action.payload;
return {
...state,
[id]: {
...state[id],
reply: [
...state[id].reply,
reply,
],
}, },
}; };
} }
case 'DIALOGUES_POST_SEND_FULFILLED': { case 'DIALOGUES_POST_SEND_FULFILLED': {
const { placeholderId } = action.meta; const id = action.meta.dialogueId;
const { mdoc } = action.payload;
return { return {
..._.omit(state, placeholderId), ...state,
[action.payload.mdoc._id]: action.payload.mdoc, [id]: {
...state[id],
messages: [
...state[id].messages,
mdoc,
],
},
}; };
} }
case 'DIALOGUES_MESSAGE_PUSH': { case 'DIALOGUES_MESSAGE_PUSH': {
const { type, data } = action.payload; const { mdoc, udoc } = action.payload;
const id = data._id; const to = mdoc.from === UserContext._id ? mdoc.to : mdoc.from;
if (type === 'new') { return {
return { ...state,
...state, [to]: {
[id]: data, ...state[to] || {},
}; udoc,
} if (type === 'reply') { messages: [
if (state[id] === undefined) { ...state[to]?.messages || [],
window.location.reload(); mdoc,
return state; ],
} },
return { };
...state,
[id]: {
...state[id],
reply: [
...state[id].reply,
data.reply[0],
],
},
};
}
return state;
} }
default: default:
return state; return state;
+6 -14
View File
@@ -7,10 +7,10 @@ export default function reducer(state = {}, action) {
return _.fromPairs(_.map(dialogues, (d) => [d._id, ''])); return _.fromPairs(_.map(dialogues, (d) => [d._id, '']));
} }
case 'DIALOGUES_CREATE': { case 'DIALOGUES_CREATE': {
const { id } = action.payload; const { user } = action.payload;
return { return {
...state, ...state,
[id]: '', [user._id]: '',
}; };
} }
case 'DIALOGUES_INPUT_CHANGED': { case 'DIALOGUES_INPUT_CHANGED': {
@@ -20,27 +20,19 @@ export default function reducer(state = {}, action) {
[id]: action.payload, [id]: action.payload,
}; };
} }
case 'DIALOGUES_POST_REPLY_FULFILLED': { case 'DIALOGUES_POST_SEND_FULFILLED': {
const id = action.meta.dialogueId; const id = action.meta.dialogueId;
return { return {
...state, ...state,
[id]: '', [id]: '',
}; };
} }
case 'DIALOGUES_POST_SEND_FULFILLED': {
const { placeholderId } = action.meta;
return {
..._.omit(state, placeholderId),
[action.payload.mdoc._id]: '',
};
}
case 'DIALOGUES_MESSAGE_PUSH': { case 'DIALOGUES_MESSAGE_PUSH': {
const { type, data } = action.payload; const { udoc } = action.payload;
const id = data._id; if (!state[udoc._id]) {
if (type === 'new') {
return { return {
...state, ...state,
[id]: '', [udoc._id]: '',
}; };
} }
return state; return state;
+13 -39
View File
@@ -8,66 +8,40 @@ export default function reducer(state = {}, action) {
return _.fromPairs(_.map(dialogues, (d) => [d._id, false])); return _.fromPairs(_.map(dialogues, (d) => [d._id, false]));
} }
case 'DIALOGUES_CREATE': { case 'DIALOGUES_CREATE': {
const { id } = action.payload; const { user } = action.payload;
return { return {
...state, ...state,
[id]: false, [user._id]: false,
}; };
} }
case 'DIALOGUES_POST_REPLY_PENDING': { case 'DIALOGUES_POST_SEND_PENDING': {
const id = action.meta.dialogueId; const id = action.meta.dialogueId;
return { return {
...state, ...state,
[id]: true, [id]: true,
}; };
} }
case 'DIALOGUES_POST_REPLY_REJECTED': {
Notification.error(action.payload.message);
const id = action.meta.dialogueId;
return {
...state,
[id]: false,
};
}
case 'DIALOGUES_POST_REPLY_FULFILLED': {
const id = action.meta.dialogueId;
return {
...state,
[id]: false,
};
}
case 'DIALOGUES_POST_SEND_PENDING': {
const { placeholderId } = action.meta;
return {
...state,
[placeholderId]: true,
};
}
case 'DIALOGUES_POST_SEND_REJECTED': { case 'DIALOGUES_POST_SEND_REJECTED': {
Notification.error(action.payload.message); Notification.error(action.payload.message);
const { placeholderId } = action.meta; const id = action.meta.dialogueId;
return { return {
...state, ...state,
[placeholderId]: false, [id]: false,
}; };
} }
case 'DIALOGUES_POST_SEND_FULFILLED': { case 'DIALOGUES_POST_SEND_FULFILLED': {
const { placeholderId } = action.meta; const id = action.meta.dialogueId;
return { return {
..._.omit(state, placeholderId), ...state,
[action.payload.mdoc._id]: false, [id]: false,
}; };
} }
case 'DIALOGUES_MESSAGE_PUSH': { case 'DIALOGUES_MESSAGE_PUSH': {
const { type, data } = action.payload; const { udoc } = action.payload;
const id = data._id; return {
if (type === 'new') { ...state,
return { [udoc._id]: false,
...state, };
[id]: false,
};
}
return state;
} }
default: default:
return state; return state;
+3 -6
View File
@@ -1,4 +1,3 @@
import _ from 'lodash';
import { NamedPage } from 'vj/misc/PageLoader'; import { NamedPage } from 'vj/misc/PageLoader';
import request from 'vj/utils/request'; import request from 'vj/utils/request';
import loadReactRedux from 'vj/utils/loadReactRedux'; import loadReactRedux from 'vj/utils/loadReactRedux';
@@ -11,17 +10,15 @@ const page = new NamedPage('home_messages', () => {
let reduxStore; let reduxStore;
function createDialog(user) { function createDialog(user) {
const id = _.uniqueId('PLACEHOLDER_');
reduxStore.dispatch({ reduxStore.dispatch({
type: 'DIALOGUES_CREATE', type: 'DIALOGUES_CREATE',
payload: { payload: {
id,
user, user,
}, },
}); });
reduxStore.dispatch({ reduxStore.dispatch({
type: 'DIALOGUES_SWITCH_TO', type: 'DIALOGUES_SWITCH_TO',
payload: id, payload: user._id,
}); });
} }
@@ -82,11 +79,11 @@ const page = new NamedPage('home_messages', () => {
*/ */
async function loadSendTarget() { async function loadSendTarget() {
const queryString = parseQueryString(); const queryString = parseQueryString();
if (!queryString.target_uid) { if (!queryString.target) {
return; return;
} }
const user = await request.get('/user/search', { const user = await request.get('/user/search', {
q: queryString.target_uid, q: queryString.target,
exact_match: true, exact_match: true,
}); });
if (!user || user.length === 0) { if (!user || user.length === 0) {