mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
messagepad (react is great!)
This commit is contained in:
1 parent
39ea30664d
commit
560e920624
13 files changed
+146
-279
No files matched your search
+45
-79
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in new issue
Block a user