diff --git a/src/components/modules/groups/GroupMembers.jsx b/src/components/modules/groups/GroupMembers.jsx
index 633b8810..3bbde6f7 100644
--- a/src/components/modules/groups/GroupMembers.jsx
+++ b/src/components/modules/groups/GroupMembers.jsx
@@ -11,6 +11,7 @@ import AccountName from 'app/components/elements/common/AccountName'
import Input from 'app/components/elements/common/Input';
import GroupMember from 'app/components/elements/groups/GroupMember'
import LoadingIndicator from 'app/components/elements/LoadingIndicator'
+import MarkNotificationRead from 'app/components/elements/MarkNotificationRead'
import { getRoleInGroup, getGroupMeta, getGroupTitle } from 'app/utils/groups'
export async function validateMembersStep(values, errors) {
@@ -253,6 +254,8 @@ class GroupMembers extends React.Component {
{this._renderMemberTypeSwitch()}
}
+ {(username && showPendings) ? : null}
}
diff --git a/src/components/modules/groups/MyGroups.jsx b/src/components/modules/groups/MyGroups.jsx
index a9a588e6..f818bf34 100644
--- a/src/components/modules/groups/MyGroups.jsx
+++ b/src/components/modules/groups/MyGroups.jsx
@@ -12,6 +12,7 @@ import user from 'app/redux/UserReducer'
import DropdownMenu from 'app/components/elements/DropdownMenu'
import Icon from 'app/components/elements/Icon'
import LoadingIndicator from 'app/components/elements/LoadingIndicator'
+import MarkNotificationRead from 'app/components/elements/MarkNotificationRead'
import { showLoginDialog } from 'app/components/dialogs/LoginDialog'
import { getGroupLogo, getGroupMeta, getRoleInGroup } from 'app/utils/groups'
@@ -244,6 +245,8 @@ class MyGroups extends React.Component {
}
+ const { username } = this.props
+
return
{tt('my_groups_jsx.title')}
@@ -251,6 +254,8 @@ class MyGroups extends React.Component {
{button}
{groups}
{hasGroups ?
: null}
+ {username ?
: null}
}
}
diff --git a/src/components/pages/Messages.jsx b/src/components/pages/Messages.jsx
index c503fbae..31bd07ee 100644
--- a/src/components/pages/Messages.jsx
+++ b/src/components/pages/Messages.jsx
@@ -29,6 +29,7 @@ import g from 'app/redux/GlobalReducer'
import transaction from 'app/redux/TransactionReducer'
import user from 'app/redux/UserReducer'
import { getRoleInGroup, opGroup } from 'app/utils/groups'
+import { parseMentions } from 'app/utils/mentions'
import { getProfileImage, } from 'app/utils/NormalizeProfile';
import { normalizeContacts, normalizeMessages, cacheMyOwnMsg } from 'app/utils/Normalizators';
import { fitToPreview } from 'app/utils/ImageUtils';
@@ -53,7 +54,7 @@ class Messages extends React.Component {
};
this.cachedProfileImages = {};
this.windowFocused = true;
- this.newMessages = 0;
+ this.newMessages = {}
if (process.env.MOBILE_APP) {
this.stopService()
}
@@ -71,21 +72,43 @@ class Messages extends React.Component {
return the_group ? the_group.name : ''
}
- markMessages() {
- const { messages } = this.state;
- if (!messages.length) return;
+ markMessages = () => {
+ const { messages } = this.props
+ if (!messages || !messages.size) return
+
+ const msgs = messages.toJS()
const { account, accounts, } = this.props;
const to = this.getToAcc()
- let OPERATIONS = golos.messages.makeDatedGroups(messages, (message_object, idx) => {
- return message_object.toMark && !message_object._offchain;
+ const isGroup = this.isGroup()
+
+ let OPERATIONS = golos.messages.makeDatedGroups(msgs, (msg, idx) => {
+ if (msg._offchain) return false
+ if (msg.read_date.startsWith('19')) {
+ if (!isGroup) {
+ return msg.to === account.name
+ } else {
+ if (msg.to === account.name) return true
+ }
+ }
+ if (isGroup && msg.mentions.includes(account.name)) {
+ return true
+ }
+ return false
}, (group, indexes, results) => {
- const json = JSON.stringify(['private_mark_message', {
- from: accounts[to].name,
- to: account.name,
+ const op = {
+ from: isGroup ? '' : accounts[to].name,
+ to: isGroup ? '' : account.name,
...group,
- }]);
+ }
+ if (isGroup) {
+ op.extensions = [[0, {
+ group: to,
+ requester: account.name,
+ }]]
+ }
+ const json = JSON.stringify(['private_mark_message', op])
return ['custom_json',
{
id: 'private_message',
@@ -93,25 +116,26 @@ class Messages extends React.Component {
json,
}
];
- }, messages.length - 1, -1);
+ }, 0, msgs.length);
this.props.sendOperations(account, accounts[to], OPERATIONS);
}
- markMessages2 = debounce(this.markMessages, 1000);
+ markMessages2 = debounce(this.markMessages, 1000)
- flashMessage() {
- ++this.newMessages;
+ flashMessage(nonce) {
+ this.newMessages[nonce] = true
- let title = this.newMessages;
- const plural = this.newMessages % 10;
+ const count = Object.keys(this.newMessages).length
+ let title = count
+ const plural = count % 10
if (plural === 1) {
- if (this.newMessages === 11)
+ if (count === 11)
title += tt('messages.new_message5');
else
title += tt('messages.new_message1');
- } else if ((plural === 2 || plural === 3 || plural === 4) && (this.newMessages < 10 || this.newMessages > 20)) {
+ } else if ((plural === 2 || plural === 3 || plural === 4) && (count < 10 || count > 20)) {
title += tt('messages.new_message234');
} else {
title += tt('messages.new_message5');
@@ -220,18 +244,20 @@ class Messages extends React.Component {
//alert(scope + ' ' + type + op +' ' + timestamp)
const isDonate = type === 'donate'
const toAcc = this.getToAcc()
- const group = opGroup(op)
+ const { group } = opGroup(op)
let updateMessage = group === this.state.to || (!group && (op.from === toAcc ||
op.to === toAcc))
const isMine = username === op.from;
if (type === 'private_message') {
if (op.update) {
this.props.messageEdited(op, timestamp, updateMessage, isMine);
- } else if (this.nonce !== op.nonce) {
+ } else {
this.props.messaged(op, timestamp, updateMessage, isMine);
- this.nonce = op.nonce
- if (!isMine && !this.windowFocused) {
- this.flashMessage();
+ if (this.nonce !== op.nonce) {
+ this.nonce = op.nonce
+ if (!isMine && !this.windowFocused) {
+ this.flashMessage(op.nonce)
+ }
}
}
} else if (type === 'private_delete_message') {
@@ -357,6 +383,7 @@ class Messages extends React.Component {
}
updateData()
}
+ this.markMessages2()
}
componentWillUnmount() {
@@ -808,7 +835,7 @@ class Messages extends React.Component {
-
+
@@ -864,7 +891,7 @@ class Messages extends React.Component {
}
let user_menu = [
- {link: '#', onClick: openMyGroups, icon: 'voters', value: tt('g.groups') + (isSmall ? (' @' + username) : '') },
+ {link: '#', onClick: openMyGroups, icon: 'voters', value: tt('g.groups') + (isSmall ? (' @' + username) : ''), addon: },
{link: accountLink, extLink: 'blogs', icon: 'new/blogging', value: tt('g.blog'), addon: },
{link: mentionsLink, extLink: 'blogs', icon: 'new/mention', value: tt('g.mentions'), addon: },
{link: donatesLink, extLink: 'wallet', icon: 'editor/coin', value: tt('g.rewards'), addon: },
@@ -906,7 +933,7 @@ class Messages extends React.Component {
{!isSmall ?
@@ -953,11 +980,11 @@ class Messages extends React.Component {
handleFocusChange = isFocused => {
this.windowFocused = isFocused;
if (!isFocused) {
- if (this.newMessages) {
+ if (Object.keys(this.newMessages).length) {
flash();
}
} else {
- this.newMessages = 0;
+ this.newMessages = {}
unflash();
}
}
@@ -1191,6 +1218,11 @@ export default withRouter(connect(
message = {...message, ...replyingMessage};
}
+ let mentions = []
+ if (group) {
+ mentions = parseMentions(message)
+ }
+
let data = null
try {
data = await golos.messages.encodeMsg({ group,
@@ -1214,7 +1246,6 @@ export default withRouter(connect(
update: editInfo ? true : false,
encrypted_message: data.encrypted_message,
}
- //alert(JSON.stringify(opData))
if (group) {
let requester
@@ -1226,9 +1257,11 @@ export default withRouter(connect(
opData.extensions = [[0, {
group: group.name,
- requester
+ requester,
+ mentions
}]]
}
+ //alert(JSON.stringify(opData))
cacheMyOwnMsg(opData, group, message)
diff --git a/src/redux/FetchDataSaga.js b/src/redux/FetchDataSaga.js
index a5a556f5..d152040a 100644
--- a/src/redux/FetchDataSaga.js
+++ b/src/redux/FetchDataSaga.js
@@ -243,6 +243,9 @@ export function* fetchMyGroups({ payload: { account } }) {
}
})
groups = [...groupsOwn, ...groups]
+ groups.sort((a, b) => {
+ return b.pendings - a.pendings
+ })
yield put(g.actions.receiveMyGroups({ groups }))
} catch (err) {
diff --git a/src/redux/GlobalReducer.js b/src/redux/GlobalReducer.js
index a17099b0..57f975f3 100644
--- a/src/redux/GlobalReducer.js
+++ b/src/redux/GlobalReducer.js
@@ -93,8 +93,9 @@ export default createModule({
message.donates = '0.000 GOLOS'
message.donates_uia = 0
}
- const group = opGroup(message)
+ const { group, mentions } = opGroup(message)
message.group = group
+ message.mentions = mentions
let new_state = state;
let messages_update = message.nonce;
@@ -199,15 +200,14 @@ export default createModule({
) => {
let new_state = state;
let messages_update = message.nonce || Math.random();
+ const { requester } = opGroup(message)
if (updateMessage) {
new_state = new_state.updateIn(['messages'],
List(),
messages => {
- return processDatedGroup(message, messages, (messages, idx) => {
- let msg = messages.get(idx)
+ return processDatedGroup(message, messages, (msg, idx) => {
msg = msg.set('read_date', timestamp)
- const msgs = messages.set(idx, msg)
- return { msgs }
+ return { updated: msg }
});
});
}
@@ -260,10 +260,8 @@ export default createModule({
new_state = new_state.updateIn(['messages'],
List(),
messages => {
- return processDatedGroup(message, messages, (messages, idx) => {
- let msg = messages.get(idx)
- const msgs = messages.delete(idx)
- return { msgs, fixIdx: idx - 1 }
+ return processDatedGroup(message, messages, (msg, idx) => {
+ return { updated: null, fixIdx: idx - 1 }
});
})
}
diff --git a/src/redux/TransactionSaga.js b/src/redux/TransactionSaga.js
index a97727c8..7a8410b4 100644
--- a/src/redux/TransactionSaga.js
+++ b/src/redux/TransactionSaga.js
@@ -46,14 +46,16 @@ function* preBroadcast_custom_json({operation}) {
const idx = msgs.findIndex(i => i.get('nonce') === json[1].nonce);
if (idx === -1) {
let group = ''
+ let mentions = []
const exts = json[1].extensions || []
for (const [key, val ] of exts) {
if (key === 0) {
group = val.group
+ mentions = val.mentions
break
}
}
- const newMsg = messageOpToObject(json[1], group)
+ const newMsg = messageOpToObject(json[1], group, mentions)
msgs = msgs.insert(0, fromJS(newMsg))
} else {
messages_update = json[1].nonce;
diff --git a/src/utils/MessageUtils.js b/src/utils/MessageUtils.js
index 8089fac0..f69da6d1 100644
--- a/src/utils/MessageUtils.js
+++ b/src/utils/MessageUtils.js
@@ -6,12 +6,20 @@ export function displayQuoteMsg(body) {
}
export function processDatedGroup(group, messages, for_each) {
+ let deleteIt
if (group.nonce) {
const idx = messages.findIndex(i => i.get('nonce') === group.nonce);
if (idx !== -1) {
messages = messages.update(idx, (msg) => {
- return for_each(msg, idx);
- });
+ const { updated, fixIdx } = for_each(msg, idx)
+ if (!updated) {
+ deleteIt = idx
+ }
+ return updated || msg
+ })
+ if (deleteIt !== undefined) {
+ messages = messages.delete(idx)
+ }
}
} else {
let inRange = false;
@@ -26,15 +34,19 @@ export function processDatedGroup(group, messages, for_each) {
break;
}
if (inRange) {
- const updated = for_each(messages, idx)
- if (updated) {
- const { msgs, fixIdx } = updated
- if (msgs) {
- messages = msgs
+ deleteIt = undefined
+ messages = messages.update(idx, (msg) => {
+ const { updated, fixIdx } = for_each(msg, idx)
+ if (!updated) {
+ deleteIt = idx
}
if (fixIdx !== undefined) {
idx = fixIdx
}
+ return updated || msg
+ })
+ if (deleteIt !== undefined) {
+ messages = messages.delete(idx)
}
}
}
diff --git a/src/utils/Normalizators.js b/src/utils/Normalizators.js
index 40a92385..3fdad45a 100644
--- a/src/utils/Normalizators.js
+++ b/src/utils/Normalizators.js
@@ -90,7 +90,7 @@ const loadFromCache = (msg, contact = false) => {
return false
}
-export function messageOpToObject(op, group) {
+export function messageOpToObject(op, group, mentions = []) {
const obj = {
nonce: op.nonce,
checksum: op.checksum,
@@ -103,7 +103,8 @@ export function messageOpToObject(op, group) {
receive_date: zeroDate,
encrypted_message: op.encrypted_message,
donates: '0.000 GOLOS',
- donates_uia: 0
+ donates_uia: 0,
+ mentions,
}
return obj
}
@@ -219,14 +220,21 @@ export async function normalizeMessages(messages, accounts, currentUser, to) {
msg.author = msg.from;
msg.date = new Date(msg.create_date + 'Z');
- if (!isGroup) {
- if (msg.to === currentAcc.name) {
- if (msg.read_date.startsWith('19')) {
- msg.toMark = true;
+ if (msg.read_date.startsWith('19')) {
+ if (!isGroup) {
+ if (msg.to === currentAcc.name) {
+ msg.toMark = true
+ } else {
+ msg.unread = true
}
} else {
- if (msg.read_date.startsWith('19')) {
- msg.unread = true;
+ if (msg.to === currentAcc.name) {
+ msg.toMark = true
+ } else if (msg.to) {
+ msg.unread = true
+ }
+ if (!msg.toMark && msg.mentions.includes(currentAcc.name)) {
+ msg.toMark = true
}
}
}
diff --git a/src/utils/groups.js b/src/utils/groups.js
index fd2878ae..b99994d9 100644
--- a/src/utils/groups.js
+++ b/src/utils/groups.js
@@ -55,23 +55,27 @@ const getRoleInGroup = (group, username) => {
const opGroup = (op) => {
let group = ''
- if (!op) return group
+ let requester = ''
+ let mentions = []
+ if (!op) return { group, requester, mentions }
const { extensions, memo } = op
if (extensions) {
for (const ext of extensions) {
- if (ext && ext[0] === 0) {
- group = (ext[1] && ext[1].group) || group
+ if (ext && ext[0] === 0 && ext[1]) {
+ group = ext[1].group || group
+ mentions = ext[1].mentions || mentions
+ requester = ext[1].requester || requester
}
}
}
- if (group) return group
+ if (group) return { group, requester, mentions }
if (memo) { // donate
const { target } = memo
if (target && target.group) {
- return target.group
+ group = target.group
}
}
- return group
+ return { group, requester, mentions }
}
export {
diff --git a/src/utils/mentions.js b/src/utils/mentions.js
new file mode 100644
index 00000000..916cd594
--- /dev/null
+++ b/src/utils/mentions.js
@@ -0,0 +1,18 @@
+
+const accountNameRegEx = /^@[a-z0-9.-]+$/
+
+// TODO: can be renderMsg which also supports links, and rendering
+export function parseMentions(message) {
+ let mentions = new Set()
+ const { body } = message
+ const lines = body.split('\n')
+ for (const line of lines) {
+ const words = line.split(' ')
+ for (let word of words) {
+ if (word.length > 3 && accountNameRegEx.test(word)) {
+ mentions.add(word.slice(1))
+ }
+ }
+ }
+ return [...mentions]
+}