diff --git a/package.json b/package.json index a1d54ed3..347170cb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "emoji-picker-element": "^1.10.1", "formik": "./git-deps/formik/packages/formik", "git-rev-sync": "^3.0.2", - "golos-lib-js": "^0.9.75", + "golos-lib-js": "^0.9.76", "history": "4.10.1", "immutable": "^4.0.0", "koa": "^2.13.4", @@ -73,7 +73,7 @@ "scripts": { "preinstall": "node git-install.js", "cordova": "cordova", - "dev": "cross-env HTTPS=true react-app-rewired start", + "dev": "cross-env react-app-rewired start", "dev:server": "nodemon server", "build": "react-app-rewired build", "prod": "NODE_ENV=production node server/index.js", diff --git a/src/components/elements/messages/ConversationListItem/ConversationListItem.css b/src/components/elements/messages/ConversationListItem/ConversationListItem.css index 20e7c582..11346a85 100644 --- a/src/components/elements/messages/ConversationListItem/ConversationListItem.css +++ b/src/components/elements/messages/ConversationListItem/ConversationListItem.css @@ -54,6 +54,11 @@ text-align: center; } +.conversation-unread.mention:not(.mine) { + background-color: #007aff; + margin-left: 8px; +} + .conversation-unread.mine { float: right; color: #007aff; diff --git a/src/components/elements/messages/ConversationListItem/index.jsx b/src/components/elements/messages/ConversationListItem/index.jsx index fe2b33ec..39145242 100644 --- a/src/components/elements/messages/ConversationListItem/index.jsx +++ b/src/components/elements/messages/ConversationListItem/index.jsx @@ -64,7 +64,7 @@ export default class ConversationListItem extends React.Component { render() { const { selected } = this.props; - const { avatar, isSystemMessage, contact, last_message, size, unread_donate } = this.props.data; + const { avatar, isSystemMessage, contact, last_message, size, unread_donate, kind } = this.props.data; const link = this.makeLink(); @@ -84,12 +84,31 @@ export default class ConversationListItem extends React.Component { unread = (
); } - const unreadMessages = size && size.unread_inbox_messages; + let title = '' + + const unreadMessages = size && size.unread_inbox_messages + const unreadMentions = size && size.unread_mentions if (!unread && unreadMessages) { unread = (
{unreadMessages}
) + if (kind === 'group') { + title += tt('plurals.reply_count', { count: unreadMessages }) + } + } + + if (unreadMentions) { + unread = +
+ {unreadMentions} +
+ {unread} +
+ if (kind === 'group') { + if (title) title += ', ' + title += tt('plurals.mention_count', { count: unreadMentions }) + } } let checkmark @@ -100,7 +119,7 @@ export default class ConversationListItem extends React.Component { } return ( - + {this._renderAvatar()}

{contact}{checkmark}

diff --git a/src/components/elements/messages/Message/Message.css b/src/components/elements/messages/Message/Message.css index a874a7d8..c758dcc2 100644 --- a/src/components/elements/messages/Message/Message.css +++ b/src/components/elements/messages/Message/Message.css @@ -44,6 +44,9 @@ text-decoration: underline; font-weight: bold; } +.msgs-message .bubble-container a.mention { + text-decoration: none; +} .msgs-message.mine .bubble-container { justify-content: flex-end; diff --git a/src/components/elements/messages/Message/index.jsx b/src/components/elements/messages/Message/index.jsx index 692b7bf3..c20463ac 100644 --- a/src/components/elements/messages/Message/index.jsx +++ b/src/components/elements/messages/Message/index.jsx @@ -2,6 +2,7 @@ import React from 'react'; import {connect} from 'react-redux' import { Fade } from 'react-foundation-components/lib/global/fade' import { LinkWithDropdown } from 'react-foundation-components/lib/global/dropdown' +import { Link } from 'react-router-dom' import tt from 'counterpart'; import cn from 'classnames' import { Asset } from 'golos-lib-js/lib/utils' @@ -9,6 +10,8 @@ import { Asset } from 'golos-lib-js/lib/utils' import AuthorDropdown from 'app/components/elements/messages/AuthorDropdown' import Donating from 'app/components/elements/messages/Donating' import Userpic from 'app/components/elements/Userpic' +import { session } from 'app/redux/UserSaga' +import { accountNameRegEx } from 'app/utils/mentions' import { displayQuoteMsg } from 'app/utils/MessageUtils'; import { proxifyImageUrl } from 'app/utils/ProxifyUrl'; import './Message.css'; @@ -26,6 +29,8 @@ class Message extends React.Component { }; render() { + let username + const { idx, data, @@ -72,6 +77,11 @@ class Message extends React.Component { } else if (word.length <= 2 && /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/.test(word)) { spans.push({word}); spans.push(' '); + } else if (word.length > 3 && accountNameRegEx.test(word)) { + const sess = session.load() + if (sess && !username) username = sess[0] + spans.push({word}) + spans.push(' ') } else { spans.push(word + ' '); } diff --git a/src/components/pages/Messages.jsx b/src/components/pages/Messages.jsx index 31bd07ee..d6e635e5 100644 --- a/src/components/pages/Messages.jsx +++ b/src/components/pages/Messages.jsx @@ -106,6 +106,7 @@ class Messages extends React.Component { op.extensions = [[0, { group: to, requester: account.name, + mentions: [account.name], }]] } const json = JSON.stringify(['private_mark_message', op]) @@ -252,7 +253,7 @@ class Messages extends React.Component { if (op.update) { this.props.messageEdited(op, timestamp, updateMessage, isMine); } else { - this.props.messaged(op, timestamp, updateMessage, isMine); + this.props.messaged(op, timestamp, updateMessage, isMine, username) if (this.nonce !== op.nonce) { this.nonce = op.nonce if (!isMine && !this.windowFocused) { @@ -1285,10 +1286,14 @@ export default withRouter(connect( successCallback: null, errorCallback: (err, errStr) => { if (err && err.message) { - if (err.message.includes('blocked by')) { + const bm = 'blocked by user (@' + const bmIdx = err.message.indexOf(bm) + if (bmIdx > -1) { + const msg = err.message.substring(bmIdx + bm.length) + const blocker = msg.substring(0, msg.indexOf(')')) this.showError(tt( 'messages.blocked_BY', { - BY: toAcc ? toAcc.name : '' + BY: blocker } ), 10000) return @@ -1307,8 +1312,8 @@ export default withRouter(connect( }, })); }, - messaged: (message, timestamp, updateMessage, isMine) => { - dispatch(g.actions.messaged({message, timestamp, updateMessage, isMine})); + messaged: (message, timestamp, updateMessage, isMine, username) => { + dispatch(g.actions.messaged({message, timestamp, updateMessage, isMine, username})); }, messageEdited: (message, timestamp, updateMessage, isMine) => { dispatch(g.actions.messageEdited({message, timestamp, updateMessage, isMine})); diff --git a/src/locales/en.json b/src/locales/en.json index b8faead7..55d6d22e 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -339,10 +339,20 @@ "one": "1 member", "other": "%(count)s members" }, + "mention_count": { + "zero": "0 mentions", + "one": "1 mention", + "other": "%(count)s mentions" + }, "message_count": { "zero": "0 messages", "one": "1 message", "other": "%(count)s messages" + }, + "reply_count": { + "zero": "0 replies", + "one": "1 reply", + "other": "%(count)s replies" } }, "g": { diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json index 384b1269..950087b8 100644 --- a/src/locales/ru-RU.json +++ b/src/locales/ru-RU.json @@ -355,10 +355,20 @@ "one": "1 участник", "other": "%(count)s участник(-ов)" }, + "mention_count": { + "zero": "0 упоминания", + "one": "1 упоминание", + "other": "%(count)s упоминания(-й)" + }, "message_count": { "zero": "0 сообщений", "one": "1 сообщение", "other": "%(count)s сообщения(-й)" + }, + "reply_count": { + "zero": "0 ответов", + "one": "1 ответ", + "other": "%(count)s ответа(-ов)" } }, "g": { diff --git a/src/redux/GlobalReducer.js b/src/redux/GlobalReducer.js index 57f975f3..5a86167f 100644 --- a/src/redux/GlobalReducer.js +++ b/src/redux/GlobalReducer.js @@ -84,7 +84,7 @@ export default createModule({ action: 'MESSAGED', reducer: ( state, - { payload: { message, timestamp, updateMessage, isMine } } + { payload: { message, timestamp, updateMessage, isMine, username } } ) => { message.create_date = timestamp; message.receive_date = timestamp; @@ -96,6 +96,7 @@ export default createModule({ const { group, mentions } = opGroup(message) message.group = group message.mentions = mentions + message.read_date = (group && !message.to) ? timestamp : '1970-01-01T00:00:00'; let new_state = state; let messages_update = message.nonce; @@ -138,9 +139,15 @@ export default createModule({ contacts = contacts.update(idx, contact => { contact = contact.set('last_message', fromJS(message)); if (!isMine && !updateMessage) { - let msgs = contact.getIn(['size', 'unread_inbox_messages']); - contact = contact.setIn(['size', 'unread_inbox_messages'], - msgs + 1); + if (!group || message.to === username) { + let msgs = contact.getIn(['size', 'unread_inbox_messages']); + contact = contact.setIn(['size', 'unread_inbox_messages'], + msgs + 1); + } + if (group && message.mentions && message.mentions.includes(username)) { + contact = contact.updateIn(['size', 'unread_mentions'], + msgs => msgs + 1) + } } return contact }); @@ -200,7 +207,7 @@ export default createModule({ ) => { let new_state = state; let messages_update = message.nonce || Math.random(); - const { requester } = opGroup(message) + const { group, requester } = opGroup(message) if (updateMessage) { new_state = new_state.updateIn(['messages'], List(), @@ -215,7 +222,7 @@ export default createModule({ List(), contacts => { let idx = contacts.findIndex(i => - i.get('contact') === (isMine ? message.to : message.from)); + i.get('contact') === (group || (isMine ? message.to : message.from))) if (idx !== -1) { contacts = contacts.update(idx, contact => { // to update read_date (need for isMine case), and more actualize text @@ -229,6 +236,9 @@ export default createModule({ // currently used only !isMine case const msgsKey = isMine ? 'unread_outbox_messages' : 'unread_inbox_messages'; contact = contact.setIn(['size', msgsKey], 0); + if (!isMine) { + contact = contact.setIn(['size', 'unread_mentions'], 0) + } return contact; }); } diff --git a/src/utils/mentions.js b/src/utils/mentions.js index 916cd594..a4232b6b 100644 --- a/src/utils/mentions.js +++ b/src/utils/mentions.js @@ -1,5 +1,5 @@ -const accountNameRegEx = /^@[a-z0-9.-]+$/ +export const accountNameRegEx = /^@[a-z0-9.-]+$/ // TODO: can be renderMsg which also supports links, and rendering export function parseMentions(message) { diff --git a/yarn.lock b/yarn.lock index 1b7a63bd..84575d8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5256,10 +5256,10 @@ globby@^11.0.1, globby@^11.0.4: "gls-messenger-native-core@file:native_core": version "1.0.0" -golos-lib-js@^0.9.75: - version "0.9.75" - resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.75.tgz#51b2f05f6c536776d5a9681f2871d4c9d3449e37" - integrity sha512-0upRVfRnCJ+MD9cMCtVCA85eWpXKTF/zg8mjhQEpfuUELFRmNQ1maDuIY/meM3VSIVgkXaoqw1ci0CHjjfP55w== +golos-lib-js@^0.9.76: + version "0.9.76" + resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.76.tgz#c589b26a8f77916529f2fb6e1020bb87f4cb0a7f" + integrity sha512-E9A9BnVoOoPjklxGJVxB3xKgLbLSCaXfW0lN4pipAKuokGEVFy8DPEwlUsFgmY9Jf9JFcwl5h6q2c1dzEuBGkQ== dependencies: abort-controller "^3.0.0" assert "^2.0.0"