[NEW] Threads (#798)
This commit is contained in:
parent
5aec0ec186
commit
9cf81bbab9
File diff suppressed because it is too large
Load Diff
|
@ -1,25 +1,5 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function messagesRequest(room) {
|
||||
return {
|
||||
type: types.MESSAGES.REQUEST,
|
||||
room
|
||||
};
|
||||
}
|
||||
|
||||
export function messagesSuccess() {
|
||||
return {
|
||||
type: types.MESSAGES.SUCCESS
|
||||
};
|
||||
}
|
||||
|
||||
export function messagesFailure(err) {
|
||||
return {
|
||||
type: types.MESSAGES.FAILURE,
|
||||
err
|
||||
};
|
||||
}
|
||||
|
||||
export function actionsShow(actionMessage) {
|
||||
return {
|
||||
type: types.MESSAGES.ACTIONS_SHOW,
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import { SERVER } from './actionsTypes';
|
||||
|
||||
export function selectServerRequest(server) {
|
||||
export function selectServerRequest(server, version, fetchVersion = true) {
|
||||
return {
|
||||
type: SERVER.SELECT_REQUEST,
|
||||
server
|
||||
server,
|
||||
version,
|
||||
fetchVersion
|
||||
};
|
||||
}
|
||||
|
||||
export function selectServerSuccess(server) {
|
||||
export function selectServerSuccess(server, version) {
|
||||
return {
|
||||
type: SERVER.SELECT_SUCCESS,
|
||||
server
|
||||
server,
|
||||
version
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -61,5 +61,8 @@ export default {
|
|||
},
|
||||
Assets_favicon_512: {
|
||||
type: null
|
||||
},
|
||||
Threads_enabled: {
|
||||
type: null
|
||||
}
|
||||
};
|
||||
|
|
|
@ -56,6 +56,7 @@ class MessageBox extends Component {
|
|||
replyMessage: PropTypes.object,
|
||||
replying: PropTypes.bool,
|
||||
editing: PropTypes.bool,
|
||||
threadsEnabled: PropTypes.bool,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
|
@ -93,7 +94,7 @@ class MessageBox extends Component {
|
|||
componentDidMount() {
|
||||
const { rid } = this.props;
|
||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
if (room.draftMessage && room.draftMessage !== '') {
|
||||
if (room && room.draftMessage) {
|
||||
this.setInput(room.draftMessage);
|
||||
this.setShowSend(true);
|
||||
}
|
||||
|
@ -571,18 +572,27 @@ class MessageBox extends Component {
|
|||
if (message.trim() === '') {
|
||||
return;
|
||||
}
|
||||
// if is editing a message
|
||||
|
||||
const {
|
||||
editing, replying
|
||||
} = this.props;
|
||||
|
||||
// Edit
|
||||
if (editing) {
|
||||
const { _id, rid } = editingMessage;
|
||||
editRequest({ _id, msg: message, rid });
|
||||
|
||||
// Reply
|
||||
} else if (replying) {
|
||||
const {
|
||||
user, replyMessage, roomType, closeReply
|
||||
} = this.props;
|
||||
const { replyMessage, closeReply, threadsEnabled } = this.props;
|
||||
|
||||
// Thread
|
||||
if (threadsEnabled) {
|
||||
onSubmit(message, replyMessage._id);
|
||||
|
||||
// Legacy reply
|
||||
} else {
|
||||
const { user, roomType } = this.props;
|
||||
const permalink = await this.getPermalink(replyMessage);
|
||||
let msg = `[ ](${ permalink }) `;
|
||||
|
||||
|
@ -593,9 +603,11 @@ class MessageBox extends Component {
|
|||
|
||||
msg = `${ msg } ${ message }`;
|
||||
onSubmit(msg);
|
||||
}
|
||||
closeReply();
|
||||
|
||||
// Normal message
|
||||
} else {
|
||||
// if is submiting a new message
|
||||
onSubmit(message);
|
||||
}
|
||||
this.clearInput();
|
||||
|
@ -820,6 +832,7 @@ const mapStateToProps = state => ({
|
|||
replying: state.messages.replyMessage && !!state.messages.replyMessage.msg,
|
||||
editing: state.messages.editing,
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
threadsEnabled: state.settings.Threads_enabled,
|
||||
user: {
|
||||
id: state.login.user && state.login.user.id,
|
||||
username: state.login.user && state.login.user.username,
|
||||
|
|
|
@ -85,8 +85,6 @@ const getInfoMessage = ({
|
|||
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
|
||||
} else if (type === 'message_snippeted') {
|
||||
return I18n.t('Created_snippet');
|
||||
} else if (type === 'thread-created') {
|
||||
return I18n.t('Thread_created', { name: msg });
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
@ -99,6 +97,7 @@ export default class Message extends PureComponent {
|
|||
baseUrl: PropTypes.string.isRequired,
|
||||
customEmojis: PropTypes.object.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
customThreadTimeFormat: PropTypes.string,
|
||||
msg: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
|
@ -137,6 +136,10 @@ export default class Message extends PureComponent {
|
|||
useRealName: PropTypes.bool,
|
||||
dcount: PropTypes.number,
|
||||
dlm: PropTypes.instanceOf(Date),
|
||||
tmid: PropTypes.string,
|
||||
tcount: PropTypes.number,
|
||||
tlm: PropTypes.instanceOf(Date),
|
||||
tmsg: PropTypes.string,
|
||||
// methods
|
||||
closeReactions: PropTypes.func,
|
||||
onErrorPress: PropTypes.func,
|
||||
|
@ -144,8 +147,10 @@ export default class Message extends PureComponent {
|
|||
onReactionLongPress: PropTypes.func,
|
||||
onReactionPress: PropTypes.func,
|
||||
onDiscussionPress: PropTypes.func,
|
||||
onThreadPress: PropTypes.func,
|
||||
replyBroadcast: PropTypes.func,
|
||||
toggleReactionPicker: PropTypes.func
|
||||
toggleReactionPicker: PropTypes.func,
|
||||
fetchThreadName: PropTypes.func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -169,6 +174,32 @@ export default class Message extends PureComponent {
|
|||
onLongPress();
|
||||
}
|
||||
|
||||
formatLastMessage = (lm) => {
|
||||
const { customThreadTimeFormat } = this.props;
|
||||
if (customThreadTimeFormat) {
|
||||
return moment(lm).format(customThreadTimeFormat);
|
||||
}
|
||||
return lm ? moment(lm).calendar(null, {
|
||||
lastDay: `[${ I18n.t('Yesterday') }]`,
|
||||
sameDay: 'h:mm A',
|
||||
lastWeek: 'dddd',
|
||||
sameElse: 'MMM D'
|
||||
}) : null;
|
||||
}
|
||||
|
||||
formatMessageCount = (count, type) => {
|
||||
const discussion = type === 'discussion';
|
||||
let text = discussion ? I18n.t('No_messages_yet') : null;
|
||||
if (count === 1) {
|
||||
text = `${ count } ${ discussion ? I18n.t('message') : I18n.t('reply') }`;
|
||||
} else if (count > 1 && count < 1000) {
|
||||
text = `${ count } ${ discussion ? I18n.t('messages') : I18n.t('replies') }`;
|
||||
} else if (count > 999) {
|
||||
text = `+999 ${ discussion ? I18n.t('messages') : I18n.t('replies') }`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
isInfoMessage = () => {
|
||||
const { type } = this.props;
|
||||
return SYSTEM_MESSAGES.includes(type);
|
||||
|
@ -369,23 +400,11 @@ export default class Message extends PureComponent {
|
|||
const {
|
||||
msg, dcount, dlm, onDiscussionPress
|
||||
} = this.props;
|
||||
const time = dlm ? moment(dlm).calendar(null, {
|
||||
lastDay: `[${ I18n.t('Yesterday') }]`,
|
||||
sameDay: 'h:mm A',
|
||||
lastWeek: 'dddd',
|
||||
sameElse: 'MMM D'
|
||||
}) : null;
|
||||
let buttonText = 'No messages yet';
|
||||
if (dcount === 1) {
|
||||
buttonText = `${ dcount } message`;
|
||||
} else if (dcount > 1 && dcount < 1000) {
|
||||
buttonText = `${ dcount } messages`;
|
||||
} else if (dcount > 999) {
|
||||
buttonText = '+999 messages';
|
||||
}
|
||||
const time = this.formatLastMessage(dlm);
|
||||
const buttonText = this.formatMessageCount(dcount, 'discussion');
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Text style={styles.textInfo}>{I18n.t('Started_discussion')}</Text>
|
||||
<Text style={styles.startedDiscussion}>{I18n.t('Started_discussion')}</Text>
|
||||
<Text style={styles.text}>{msg}</Text>
|
||||
<View style={styles.buttonContainer}>
|
||||
<Touchable
|
||||
|
@ -405,6 +424,56 @@ export default class Message extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderThread = () => {
|
||||
const {
|
||||
tcount, tlm, onThreadPress, msg
|
||||
} = this.props;
|
||||
|
||||
if (!tlm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const time = this.formatLastMessage(tlm);
|
||||
const buttonText = this.formatMessageCount(tcount, 'thread');
|
||||
return (
|
||||
<View style={styles.buttonContainer}>
|
||||
<Touchable
|
||||
onPress={onThreadPress}
|
||||
background={Touchable.Ripple('#fff')}
|
||||
style={[styles.button, styles.smallButton]}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
testID={`message-thread-button-${ msg }`}
|
||||
>
|
||||
<React.Fragment>
|
||||
<CustomIcon name='thread' size={20} style={styles.buttonIcon} />
|
||||
<Text style={styles.buttonText}>{buttonText}</Text>
|
||||
</React.Fragment>
|
||||
</Touchable>
|
||||
<Text style={styles.time}>{time}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderRepliedThread = () => {
|
||||
const {
|
||||
tmid, tmsg, header, onThreadPress, fetchThreadName
|
||||
} = this.props;
|
||||
if (!tmid || !header || this.isTemp()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!tmsg) {
|
||||
fetchThreadName(tmid);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style={styles.repliedThread} numberOfLines={3} testID={`message-thread-replied-on-${ tmsg }`}>
|
||||
{I18n.t('Replied_on')} <Text style={styles.repliedThreadName} onPress={onThreadPress}>{tmsg}</Text>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
renderInner = () => {
|
||||
const { type } = this.props;
|
||||
if (type === 'discussion-created') {
|
||||
|
@ -418,9 +487,11 @@ export default class Message extends PureComponent {
|
|||
return (
|
||||
<React.Fragment>
|
||||
{this.renderUsername()}
|
||||
{this.renderRepliedThread()}
|
||||
{this.renderContent()}
|
||||
{this.renderAttachment()}
|
||||
{this.renderUrl()}
|
||||
{this.renderThread()}
|
||||
{this.renderReactions()}
|
||||
{this.renderBroadcastReply()}
|
||||
</React.Fragment>
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
replyBroadcast as replyBroadcastAction
|
||||
} from '../../actions/messages';
|
||||
import { vibrate } from '../../utils/vibration';
|
||||
import debounce from '../../utils/debounce';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
|
@ -27,15 +28,14 @@ import { vibrate } from '../../utils/vibration';
|
|||
export default class MessageContainer extends React.Component {
|
||||
static propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
reactions: PropTypes.any.isRequired,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
token: PropTypes.string.isRequired
|
||||
}),
|
||||
customTimeFormat: PropTypes.string,
|
||||
customThreadTimeFormat: PropTypes.string,
|
||||
style: ViewPropTypes.style,
|
||||
status: PropTypes.number,
|
||||
archived: PropTypes.bool,
|
||||
broadcast: PropTypes.bool,
|
||||
previousItem: PropTypes.object,
|
||||
|
@ -47,6 +47,8 @@ export default class MessageContainer extends React.Component {
|
|||
Message_TimeFormat: PropTypes.string,
|
||||
editingMessage: PropTypes.object,
|
||||
useRealName: PropTypes.bool,
|
||||
status: PropTypes.number,
|
||||
navigation: PropTypes.object,
|
||||
// methods - props
|
||||
onLongPress: PropTypes.func,
|
||||
onReactionPress: PropTypes.func,
|
||||
|
@ -54,7 +56,8 @@ export default class MessageContainer extends React.Component {
|
|||
// methods - redux
|
||||
errorActionsShow: PropTypes.func,
|
||||
replyBroadcast: PropTypes.func,
|
||||
toggleReactionPicker: PropTypes.func
|
||||
toggleReactionPicker: PropTypes.func,
|
||||
fetchThreadName: PropTypes.func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -73,7 +76,7 @@ export default class MessageContainer extends React.Component {
|
|||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { reactionsModal } = this.state;
|
||||
const {
|
||||
status, reactions, broadcast, _updatedAt, editingMessage, item
|
||||
status, editingMessage, item, _updatedAt
|
||||
} = this.props;
|
||||
|
||||
if (reactionsModal !== nextState.reactionsModal) {
|
||||
|
@ -82,16 +85,10 @@ export default class MessageContainer extends React.Component {
|
|||
if (status !== nextProps.status) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
if (!!_updatedAt ^ !!nextProps._updatedAt) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(reactions, nextProps.reactions)) {
|
||||
return true;
|
||||
}
|
||||
if (broadcast !== nextProps.broadcast) {
|
||||
if (item.tmsg !== nextProps.item.tmsg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!equal(editingMessage, nextProps.editingMessage)) {
|
||||
if (nextProps.editingMessage && nextProps.editingMessage._id === item._id) {
|
||||
return true;
|
||||
|
@ -99,7 +96,7 @@ export default class MessageContainer extends React.Component {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
return _updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
||||
return _updatedAt.toISOString() !== nextProps._updatedAt.toISOString();
|
||||
}
|
||||
|
||||
onLongPress = () => {
|
||||
|
@ -127,6 +124,20 @@ export default class MessageContainer extends React.Component {
|
|||
onDiscussionPress(item);
|
||||
}
|
||||
|
||||
onThreadPress = debounce(() => {
|
||||
const { navigation, item } = this.props;
|
||||
if (item.tmid) {
|
||||
navigation.push('RoomView', {
|
||||
rid: item.rid, tmid: item.tmid, name: item.tmsg, t: 'thread'
|
||||
});
|
||||
} else if (item.tlm) {
|
||||
const title = item.msg || (item.attachments && item.attachments.length && item.attachments[0].title);
|
||||
navigation.push('RoomView', {
|
||||
rid: item.rid, tmid: item._id, name: title, t: 'thread'
|
||||
});
|
||||
}
|
||||
}, 1000, true)
|
||||
|
||||
get timeFormat() {
|
||||
const { customTimeFormat, Message_TimeFormat } = this.props;
|
||||
return customTimeFormat || Message_TimeFormat;
|
||||
|
@ -145,6 +156,7 @@ export default class MessageContainer extends React.Component {
|
|||
&& (previousItem.u.username === item.u.username)
|
||||
&& !(previousItem.groupable === false || item.groupable === false || broadcast === true)
|
||||
&& (item.ts - previousItem.ts < Message_GroupingPeriod * 1000)
|
||||
&& (previousItem.tmid === item.tmid)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -169,14 +181,15 @@ export default class MessageContainer extends React.Component {
|
|||
render() {
|
||||
const { reactionsModal } = this.state;
|
||||
const {
|
||||
item, editingMessage, user, style, archived, baseUrl, customEmojis, useRealName, broadcast
|
||||
item, editingMessage, user, style, archived, baseUrl, customEmojis, useRealName, broadcast, fetchThreadName, customThreadTimeFormat
|
||||
} = this.props;
|
||||
const {
|
||||
msg, ts, attachments, urls, reactions, t, status, avatar, u, alias, editedBy, role, drid, dcount, dlm
|
||||
_id, msg, ts, attachments, urls, reactions, t, status, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg
|
||||
} = item;
|
||||
const isEditing = editingMessage._id === item._id;
|
||||
return (
|
||||
<Message
|
||||
id={_id}
|
||||
msg={msg}
|
||||
author={u}
|
||||
ts={ts}
|
||||
|
@ -192,6 +205,7 @@ export default class MessageContainer extends React.Component {
|
|||
user={user}
|
||||
edited={editedBy && !!editedBy.username}
|
||||
timeFormat={this.timeFormat}
|
||||
customThreadTimeFormat={customThreadTimeFormat}
|
||||
style={style}
|
||||
archived={archived}
|
||||
broadcast={broadcast}
|
||||
|
@ -203,6 +217,11 @@ export default class MessageContainer extends React.Component {
|
|||
drid={drid}
|
||||
dcount={dcount}
|
||||
dlm={dlm}
|
||||
tmid={tmid}
|
||||
tcount={tcount}
|
||||
tlm={tlm}
|
||||
tmsg={tmsg}
|
||||
fetchThreadName={fetchThreadName}
|
||||
closeReactions={this.closeReactions}
|
||||
onErrorPress={this.onErrorPress}
|
||||
onLongPress={this.onLongPress}
|
||||
|
@ -211,6 +230,7 @@ export default class MessageContainer extends React.Component {
|
|||
replyBroadcast={this.replyBroadcast}
|
||||
toggleReactionPicker={this.toggleReactionPicker}
|
||||
onDiscussionPress={this.onDiscussionPress}
|
||||
onThreadPress={this.onThreadPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: COLOR_PRIMARY,
|
||||
borderRadius: 4
|
||||
borderRadius: 2
|
||||
},
|
||||
smallButton: {
|
||||
height: 30
|
||||
|
@ -200,6 +200,13 @@ export default StyleSheet.create({
|
|||
color: COLOR_PRIMARY,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
startedDiscussion: {
|
||||
fontStyle: 'italic',
|
||||
fontSize: 16,
|
||||
marginBottom: 6,
|
||||
...sharedStyles.textColorDescription,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
time: {
|
||||
fontSize: 12,
|
||||
paddingLeft: 10,
|
||||
|
@ -207,5 +214,16 @@ export default StyleSheet.create({
|
|||
...sharedStyles.textColorDescription,
|
||||
...sharedStyles.textRegular,
|
||||
fontWeight: '300'
|
||||
},
|
||||
repliedThread: {
|
||||
fontSize: 16,
|
||||
marginBottom: 6,
|
||||
...sharedStyles.textColorDescription,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
repliedThreadName: {
|
||||
fontSize: 16,
|
||||
color: COLOR_PRIMARY,
|
||||
...sharedStyles.textSemibold
|
||||
}
|
||||
});
|
||||
|
|
|
@ -124,6 +124,7 @@ export default {
|
|||
Connect: 'Connect',
|
||||
Connect_to_a_server: 'Connect to a server',
|
||||
Connected: 'Connected',
|
||||
connecting_server: 'connecting to server',
|
||||
Connecting: 'Connecting...',
|
||||
Continue_with: 'Continue with',
|
||||
Copied_to_clipboard: 'Copied to clipboard!',
|
||||
|
@ -198,6 +199,8 @@ export default {
|
|||
Message_actions: 'Message actions',
|
||||
Message_pinned: 'Message pinned',
|
||||
Message_removed: 'Message removed',
|
||||
message: 'message',
|
||||
messages: 'messages',
|
||||
Messages: 'Messages',
|
||||
Microphone_Permission_Message: 'Rocket Chat needs access to your microphone so you can send audio message.',
|
||||
Microphone_Permission: 'Microphone Permission',
|
||||
|
@ -217,10 +220,12 @@ export default {
|
|||
No_pinned_messages: 'No pinned messages',
|
||||
No_results_found: 'No results found',
|
||||
No_starred_messages: 'No starred messages',
|
||||
No_thread_messages: 'No thread messages',
|
||||
No_announcement_provided: 'No announcement provided.',
|
||||
No_description_provided: 'No description provided.',
|
||||
No_topic_provided: 'No topic provided.',
|
||||
No_Message: 'No Message',
|
||||
No_messages_yet: 'No messages yet',
|
||||
No_Reactions: 'No Reactions',
|
||||
Not_logged: 'Not logged',
|
||||
Nothing_to_save: 'Nothing to save!',
|
||||
|
@ -256,6 +261,9 @@ export default {
|
|||
Read_Only: 'Read Only',
|
||||
Register: 'Register',
|
||||
Repeat_Password: 'Repeat Password',
|
||||
Replied_on: 'Replied on:',
|
||||
replies: 'replies',
|
||||
reply: 'reply',
|
||||
Reply: 'Reply',
|
||||
Resend: 'Resend',
|
||||
Reset_password: 'Reset password',
|
||||
|
@ -311,7 +319,8 @@ export default {
|
|||
There_was_an_error_while_action: 'There was an error while {{action}}!',
|
||||
This_room_is_blocked: 'This room is blocked',
|
||||
This_room_is_read_only: 'This room is read only',
|
||||
Thread_created: 'Started a new thread: "{{name}}"',
|
||||
Thread: 'Thread',
|
||||
Threads: 'Threads',
|
||||
Timezone: 'Timezone',
|
||||
Toggle_Drawer: 'Toggle_Drawer',
|
||||
topic: 'topic',
|
||||
|
|
|
@ -131,6 +131,7 @@ export default {
|
|||
Connect: 'Conectar',
|
||||
Connect_to_a_server: 'Conectar a um servidor',
|
||||
Connected: 'Conectado',
|
||||
connecting_server: 'conectando no servidor',
|
||||
Connecting: 'Conectando...',
|
||||
Continue_with: 'Entrar com',
|
||||
Copied_to_clipboard: 'Copiado para a área de transferência!',
|
||||
|
@ -202,6 +203,8 @@ export default {
|
|||
Message_actions: 'Ações',
|
||||
Message_pinned: 'Fixou uma mensagem',
|
||||
Message_removed: 'Mensagem removida',
|
||||
message: 'mensagem',
|
||||
messages: 'mensagens',
|
||||
Messages: 'Mensagens',
|
||||
Microphone_Permission_Message: 'Rocket Chat precisa de acesso ao seu microfone para enviar mensagens de áudio.',
|
||||
Microphone_Permission: 'Acesso ao Microfone',
|
||||
|
@ -220,10 +223,12 @@ export default {
|
|||
No_pinned_messages: 'Não há mensagens fixadas',
|
||||
No_results_found: 'Nenhum resultado encontrado',
|
||||
No_starred_messages: 'Não há mensagens favoritas',
|
||||
No_thread_messages: 'Não há tópicos',
|
||||
No_announcement_provided: 'Sem anúncio.',
|
||||
No_description_provided: 'Sem descrição.',
|
||||
No_topic_provided: 'Sem tópico.',
|
||||
No_Message: 'Não há mensagens',
|
||||
No_messages_yet: 'Não há mensagens ainda',
|
||||
No_Reactions: 'Sem reações',
|
||||
Nothing_to_save: 'Nada para salvar!',
|
||||
Notify_active_in_this_room: 'Notificar usuários ativos nesta sala',
|
||||
|
@ -258,6 +263,9 @@ export default {
|
|||
Read_Only: 'Somente Leitura',
|
||||
Register: 'Registrar',
|
||||
Repeat_Password: 'Repetir Senha',
|
||||
Replied_on: 'Respondido em:',
|
||||
replies: 'respostas',
|
||||
reply: 'resposta',
|
||||
Reply: 'Responder',
|
||||
Resend: 'Reenviar',
|
||||
Reset_password: 'Resetar senha',
|
||||
|
@ -310,7 +318,8 @@ export default {
|
|||
There_was_an_error_while_action: 'Aconteceu um erro {{action}}!',
|
||||
This_room_is_blocked: 'Este quarto está bloqueado',
|
||||
This_room_is_read_only: 'Este quarto é apenas de leitura',
|
||||
Thread_created: 'Iniciou uma thread: "{{name}}"',
|
||||
Thread: 'Tópico',
|
||||
Threads: 'Tópicos',
|
||||
Timezone: 'Fuso horário',
|
||||
topic: 'tópico',
|
||||
Topic: 'Tópico',
|
||||
|
|
|
@ -29,6 +29,7 @@ import MentionedMessagesView from './views/MentionedMessagesView';
|
|||
import StarredMessagesView from './views/StarredMessagesView';
|
||||
import SearchMessagesView from './views/SearchMessagesView';
|
||||
import PinnedMessagesView from './views/PinnedMessagesView';
|
||||
import ThreadMessagesView from './views/ThreadMessagesView';
|
||||
import SelectedUsersView from './views/SelectedUsersView';
|
||||
import CreateChannelView from './views/CreateChannelView';
|
||||
import LegalView from './views/LegalView';
|
||||
|
@ -122,7 +123,8 @@ const ChatsStack = createStackNavigator({
|
|||
StarredMessagesView,
|
||||
SearchMessagesView,
|
||||
PinnedMessagesView,
|
||||
SelectedUsersView
|
||||
SelectedUsersView,
|
||||
ThreadMessagesView
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader
|
||||
});
|
||||
|
|
|
@ -39,8 +39,13 @@ export default function loadMessagesForRoom(...args) {
|
|||
if (data && data.length) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
database.write(() => data.forEach((message) => {
|
||||
message = buildMessage(message);
|
||||
try {
|
||||
database.create('messages', buildMessage(message), true);
|
||||
database.create('messages', message, true);
|
||||
// if it's a thread "header"
|
||||
if (message.tlm) {
|
||||
database.create('threads', message, true);
|
||||
}
|
||||
} catch (e) {
|
||||
log('loadMessagesForRoom -> create messages', e);
|
||||
}
|
||||
|
|
|
@ -31,11 +31,15 @@ export default function loadMissedMessages(...args) {
|
|||
if (data) {
|
||||
if (data.updated && data.updated.length) {
|
||||
const { updated } = data;
|
||||
updated.forEach(buildMessage);
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
database.write(() => updated.forEach((message) => {
|
||||
try {
|
||||
message = buildMessage(message);
|
||||
database.create('messages', message, true);
|
||||
// if it's a thread "header"
|
||||
if (message.tlm) {
|
||||
database.create('threads', message, true);
|
||||
}
|
||||
} catch (e) {
|
||||
log('loadMissedMessages -> create messages', e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { InteractionManager } from 'react-native';
|
||||
import EJSON from 'ejson';
|
||||
|
||||
import buildMessage from './helpers/buildMessage';
|
||||
import database from '../realm';
|
||||
import log from '../../utils/log';
|
||||
|
||||
async function load({ tmid, skip }) {
|
||||
try {
|
||||
// RC 1.0
|
||||
const data = await this.sdk.methodCall('getThreadMessages', { tmid, limit: 50, skip });
|
||||
if (!data || data.status === 'error') {
|
||||
return [];
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default function loadThreadMessages({ tmid, skip }) {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const data = await load.call(this, { tmid, skip });
|
||||
|
||||
if (data && data.length) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
database.write(() => data.forEach((m) => {
|
||||
try {
|
||||
const message = buildMessage(EJSON.fromJSONValue(m));
|
||||
message.rid = tmid;
|
||||
database.create('threadMessages', message, true);
|
||||
} catch (e) {
|
||||
log('loadThreadMessages -> create messages', e);
|
||||
}
|
||||
}));
|
||||
return resolve(data);
|
||||
});
|
||||
} else {
|
||||
return resolve([]);
|
||||
}
|
||||
} catch (e) {
|
||||
log('loadThreadMessages', e);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -5,12 +5,13 @@ import reduxStore from '../createStore';
|
|||
import log from '../../utils/log';
|
||||
import random from '../../utils/random';
|
||||
|
||||
export const getMessage = (rid, msg = {}) => {
|
||||
export const getMessage = (rid, msg = '', tmid) => {
|
||||
const _id = random(17);
|
||||
const message = {
|
||||
_id,
|
||||
rid,
|
||||
msg,
|
||||
tmid,
|
||||
ts: new Date(),
|
||||
_updatedAt: new Date(),
|
||||
status: messagesStatus.TEMP,
|
||||
|
@ -30,20 +31,28 @@ export const getMessage = (rid, msg = {}) => {
|
|||
};
|
||||
|
||||
export async function sendMessageCall(message) {
|
||||
const { _id, rid, msg } = message;
|
||||
const {
|
||||
_id, rid, msg, tmid
|
||||
} = message;
|
||||
// RC 0.60.0
|
||||
const data = await this.sdk.post('chat.sendMessage', { message: { _id, rid, msg } });
|
||||
const data = await this.sdk.post('chat.sendMessage', {
|
||||
message: {
|
||||
_id, rid, msg, tmid
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export default async function(rid, msg) {
|
||||
export default async function(rid, msg, tmid) {
|
||||
try {
|
||||
const message = getMessage(rid, msg);
|
||||
const message = getMessage(rid, msg, tmid);
|
||||
const [room] = database.objects('subscriptions').filtered('rid == $0', rid);
|
||||
|
||||
if (room) {
|
||||
database.write(() => {
|
||||
room.draftMessage = null;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = await sendMessageCall.call(this, message);
|
||||
|
|
|
@ -4,6 +4,7 @@ import log from '../../../utils/log';
|
|||
import protectedFunction from '../helpers/protectedFunction';
|
||||
import buildMessage from '../helpers/buildMessage';
|
||||
import database from '../../realm';
|
||||
import debounce from '../../../utils/debounce';
|
||||
|
||||
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
||||
const removeListener = listener => listener.stop();
|
||||
|
@ -107,27 +108,47 @@ export default function subscribeRoom({ rid }) {
|
|||
const { _id } = ddpMessage.fields.args[0];
|
||||
const message = database.objects('messages').filtered('_id = $0', _id);
|
||||
database.delete(message);
|
||||
const thread = database.objects('threads').filtered('_id = $0', _id);
|
||||
database.delete(thread);
|
||||
const threadMessage = database.objects('threadMessages').filtered('_id = $0', _id);
|
||||
database.delete(threadMessage);
|
||||
const cleanTmids = database.objects('messages').filtered('tmid = $0', _id).snapshot();
|
||||
if (cleanTmids && cleanTmids.length) {
|
||||
cleanTmids.forEach((m) => {
|
||||
m.tmid = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const read = debounce(() => {
|
||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
if (room._id) {
|
||||
this.readMessages(rid);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const handleMessageReceived = protectedFunction((ddpMessage) => {
|
||||
const message = buildMessage(ddpMessage.fields.args[0]);
|
||||
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
||||
if (rid !== message.rid) {
|
||||
return;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
database.write(() => {
|
||||
database.create('messages', EJSON.fromJSONValue(message), true);
|
||||
database.create('messages', message, true);
|
||||
// if it's a thread "header"
|
||||
if (message.tlm) {
|
||||
database.create('threads', message, true);
|
||||
} else if (message.tmid) {
|
||||
message.rid = message.tmid;
|
||||
database.create('threadMessages', message, true);
|
||||
}
|
||||
});
|
||||
|
||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
|
||||
if (room._id) {
|
||||
this.readMessages(rid);
|
||||
}
|
||||
read();
|
||||
} catch (e) {
|
||||
console.warn('handleMessageReceived', e);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ const serversSchema = {
|
|||
id: 'string',
|
||||
name: { type: 'string', optional: true },
|
||||
iconURL: { type: 'string', optional: true },
|
||||
roomsUpdatedAt: { type: 'date', optional: true }
|
||||
roomsUpdatedAt: { type: 'date', optional: true },
|
||||
version: 'string?'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -206,8 +207,6 @@ const messagesSchema = {
|
|||
rid: { type: 'string', indexed: true },
|
||||
ts: 'date',
|
||||
u: 'users',
|
||||
// mentions: [],
|
||||
// channels: [],
|
||||
alias: { type: 'string', optional: true },
|
||||
parseUrls: { type: 'bool', optional: true },
|
||||
groupable: { type: 'bool', optional: true },
|
||||
|
@ -223,7 +222,70 @@ const messagesSchema = {
|
|||
role: { type: 'string', optional: true },
|
||||
drid: { type: 'string', optional: true },
|
||||
dcount: { type: 'int', optional: true },
|
||||
dlm: { type: 'date', optional: true }
|
||||
dlm: { type: 'date', optional: true },
|
||||
tmid: { type: 'string', optional: true },
|
||||
tcount: { type: 'int', optional: true },
|
||||
tlm: { type: 'date', optional: true },
|
||||
replies: 'string[]'
|
||||
}
|
||||
};
|
||||
|
||||
const threadsSchema = {
|
||||
name: 'threads',
|
||||
primaryKey: '_id',
|
||||
properties: {
|
||||
_id: 'string',
|
||||
msg: { type: 'string', optional: true },
|
||||
t: { type: 'string', optional: true },
|
||||
rid: { type: 'string', indexed: true },
|
||||
ts: 'date',
|
||||
u: 'users',
|
||||
alias: { type: 'string', optional: true },
|
||||
parseUrls: { type: 'bool', optional: true },
|
||||
groupable: { type: 'bool', optional: true },
|
||||
avatar: { type: 'string', optional: true },
|
||||
attachments: { type: 'list', objectType: 'attachment' },
|
||||
urls: { type: 'list', objectType: 'url', default: [] },
|
||||
_updatedAt: { type: 'date', optional: true },
|
||||
status: { type: 'int', optional: true },
|
||||
pinned: { type: 'bool', optional: true },
|
||||
starred: { type: 'bool', optional: true },
|
||||
editedBy: 'messagesEditedBy',
|
||||
reactions: { type: 'list', objectType: 'messagesReactions' },
|
||||
role: { type: 'string', optional: true },
|
||||
drid: { type: 'string', optional: true },
|
||||
dcount: { type: 'int', optional: true },
|
||||
dlm: { type: 'date', optional: true },
|
||||
tmid: { type: 'string', optional: true },
|
||||
tcount: { type: 'int', optional: true },
|
||||
tlm: { type: 'date', optional: true },
|
||||
replies: 'string[]'
|
||||
}
|
||||
};
|
||||
|
||||
const threadMessagesSchema = {
|
||||
name: 'threadMessages',
|
||||
primaryKey: '_id',
|
||||
properties: {
|
||||
_id: 'string',
|
||||
msg: { type: 'string', optional: true },
|
||||
t: { type: 'string', optional: true },
|
||||
rid: { type: 'string', indexed: true },
|
||||
ts: 'date',
|
||||
u: 'users',
|
||||
alias: { type: 'string', optional: true },
|
||||
parseUrls: { type: 'bool', optional: true },
|
||||
groupable: { type: 'bool', optional: true },
|
||||
avatar: { type: 'string', optional: true },
|
||||
attachments: { type: 'list', objectType: 'attachment' },
|
||||
urls: { type: 'list', objectType: 'url', default: [] },
|
||||
_updatedAt: { type: 'date', optional: true },
|
||||
status: { type: 'int', optional: true },
|
||||
pinned: { type: 'bool', optional: true },
|
||||
starred: { type: 'bool', optional: true },
|
||||
editedBy: 'messagesEditedBy',
|
||||
reactions: { type: 'list', objectType: 'messagesReactions' },
|
||||
role: { type: 'string', optional: true }
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -296,6 +358,8 @@ const schema = [
|
|||
subscriptionSchema,
|
||||
subscriptionRolesSchema,
|
||||
messagesSchema,
|
||||
threadsSchema,
|
||||
threadMessagesSchema,
|
||||
usersSchema,
|
||||
roomsSchema,
|
||||
attachment,
|
||||
|
@ -323,9 +387,9 @@ class DB {
|
|||
schema: [
|
||||
serversSchema
|
||||
],
|
||||
schemaVersion: 2,
|
||||
schemaVersion: 4,
|
||||
migration: (oldRealm, newRealm) => {
|
||||
if (oldRealm.schemaVersion === 1 && newRealm.schemaVersion === 2) {
|
||||
if (oldRealm.schemaVersion >= 1 && newRealm.schemaVersion <= 3) {
|
||||
const newServers = newRealm.objects('servers');
|
||||
|
||||
// eslint-disable-next-line no-plusplus
|
||||
|
@ -363,6 +427,10 @@ class DB {
|
|||
return this.database.objects(...args);
|
||||
}
|
||||
|
||||
objectForPrimaryKey(...args) {
|
||||
return this.database.objectForPrimaryKey(...args);
|
||||
}
|
||||
|
||||
get database() {
|
||||
return this.databases.activeDB;
|
||||
}
|
||||
|
@ -376,9 +444,9 @@ class DB {
|
|||
return this.databases.activeDB = new Realm({
|
||||
path: `${ path }.realm`,
|
||||
schema,
|
||||
schemaVersion: 4,
|
||||
schemaVersion: 6,
|
||||
migration: (oldRealm, newRealm) => {
|
||||
if (oldRealm.schemaVersion === 3 && newRealm.schemaVersion === 4) {
|
||||
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 6) {
|
||||
const newSubs = newRealm.objects('subscriptions');
|
||||
|
||||
// eslint-disable-next-line no-plusplus
|
||||
|
|
|
@ -9,6 +9,7 @@ import messagesStatus from '../constants/messagesStatus';
|
|||
import database, { safeAddListener } from './realm';
|
||||
import log from '../utils/log';
|
||||
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
||||
import EventEmitter from '../utils/events';
|
||||
|
||||
import {
|
||||
setUser, setLoginServices, loginRequest, loginFailure, logout
|
||||
|
@ -31,6 +32,7 @@ import canOpenRoom from './methods/canOpenRoom';
|
|||
|
||||
import loadMessagesForRoom from './methods/loadMessagesForRoom';
|
||||
import loadMissedMessages from './methods/loadMissedMessages';
|
||||
import loadThreadMessages from './methods/loadThreadMessages';
|
||||
|
||||
import sendMessage, { getMessage, sendMessageCall } from './methods/sendMessage';
|
||||
import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage';
|
||||
|
@ -78,26 +80,24 @@ const RocketChat = {
|
|||
console.warn(`AsyncStorage error: ${ error.message }`);
|
||||
}
|
||||
},
|
||||
async testServer(server) {
|
||||
async getServerInfo(server) {
|
||||
try {
|
||||
const result = await fetch(`${ server }/api/v1/info`).then(response => response.json());
|
||||
if (result.success && result.info) {
|
||||
if (semver.lt(result.info.version, MIN_ROCKETCHAT_VERSION)) {
|
||||
const result = await fetch(`${ server }/api/info`).then(response => response.json());
|
||||
if (result.success) {
|
||||
if (semver.lt(result.version, MIN_ROCKETCHAT_VERSION)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Invalid_server_version',
|
||||
messageOptions: {
|
||||
currentVersion: result.info.version,
|
||||
currentVersion: result.version,
|
||||
minVersion: MIN_ROCKETCHAT_VERSION
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true
|
||||
};
|
||||
return result;
|
||||
}
|
||||
} catch (e) {
|
||||
log('testServer', e);
|
||||
log('getServerInfo', e);
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
|
@ -135,6 +135,7 @@ const RocketChat = {
|
|||
}
|
||||
},
|
||||
async loginSuccess({ user }) {
|
||||
EventEmitter.emit('connected');
|
||||
reduxStore.dispatch(setUser(user));
|
||||
reduxStore.dispatch(roomsRequest());
|
||||
|
||||
|
@ -370,6 +371,7 @@ const RocketChat = {
|
|||
},
|
||||
loadMissedMessages,
|
||||
loadMessagesForRoom,
|
||||
loadThreadMessages,
|
||||
getMessage,
|
||||
sendMessage,
|
||||
getRooms,
|
||||
|
@ -568,9 +570,9 @@ const RocketChat = {
|
|||
// RC 0.64.0
|
||||
return this.sdk.post('rooms.favorite', { roomId, favorite });
|
||||
},
|
||||
getRoomMembers(rid, allUsers) {
|
||||
getRoomMembers(rid, allUsers, skip = 0, limit = 10) {
|
||||
// RC 0.42.0
|
||||
return this.sdk.methodCall('getUsersOfRoom', rid, allUsers);
|
||||
return this.sdk.methodCall('getUsersOfRoom', rid, allUsers, { skip, limit });
|
||||
},
|
||||
getUserRoles() {
|
||||
// RC 0.27.0
|
||||
|
@ -649,6 +651,10 @@ const RocketChat = {
|
|||
// RC 0.51.0
|
||||
return this.sdk.methodCall('addUsersToRoom', { rid, users });
|
||||
},
|
||||
getSingleMessage(msgId) {
|
||||
// RC 0.57.0
|
||||
return this.sdk.methodCall('getSingleMessage', msgId);
|
||||
},
|
||||
hasPermission(permissions, rid) {
|
||||
let roles = [];
|
||||
try {
|
||||
|
@ -768,6 +774,17 @@ const RocketChat = {
|
|||
roomId,
|
||||
searchText
|
||||
});
|
||||
},
|
||||
toggleFollowMessage(mid, follow) {
|
||||
// RC 1.0
|
||||
if (follow) {
|
||||
return this.sdk.methodCall('followMessage', { mid });
|
||||
}
|
||||
return this.sdk.methodCall('unfollowMessage', { mid });
|
||||
},
|
||||
getThreadsList({ rid, limit, skip }) {
|
||||
// RC 1.0
|
||||
return this.sdk.methodCall('getThreadsList', { rid, limit, skip });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import * as types from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
isFetching: false,
|
||||
failure: false,
|
||||
message: {},
|
||||
actionMessage: {},
|
||||
replyMessage: {},
|
||||
|
@ -14,23 +12,6 @@ const initialState = {
|
|||
|
||||
export default function messages(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case types.MESSAGES.REQUEST:
|
||||
return {
|
||||
...state,
|
||||
isFetching: true
|
||||
};
|
||||
case types.MESSAGES.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
isFetching: false
|
||||
};
|
||||
case types.LOGIN.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
isFetching: false,
|
||||
failure: true,
|
||||
errorMessage: action.err
|
||||
};
|
||||
case types.MESSAGES.ACTIONS_SHOW:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -5,6 +5,7 @@ const initialState = {
|
|||
connected: false,
|
||||
failure: false,
|
||||
server: '',
|
||||
version: null,
|
||||
loading: true,
|
||||
adding: false
|
||||
};
|
||||
|
@ -29,6 +30,7 @@ export default function server(state = initialState, action) {
|
|||
return {
|
||||
...state,
|
||||
server: action.server,
|
||||
version: action.version,
|
||||
connecting: true,
|
||||
connected: false,
|
||||
loading: true
|
||||
|
@ -37,6 +39,7 @@ export default function server(state = initialState, action) {
|
|||
return {
|
||||
...state,
|
||||
server: action.server,
|
||||
version: action.version,
|
||||
connecting: false,
|
||||
connected: true,
|
||||
loading: false
|
||||
|
|
|
@ -69,7 +69,7 @@ const handleOpen = function* handleOpen({ params }) {
|
|||
yield navigate({ params });
|
||||
} else {
|
||||
// if deep link is from a different server
|
||||
const result = yield RocketChat.testServer(server);
|
||||
const result = yield RocketChat.getServerInfo(server);
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { APP } from '../actions/actionsTypes';
|
|||
import RocketChat from '../lib/rocketchat';
|
||||
import log from '../utils/log';
|
||||
import Navigation from '../lib/Navigation';
|
||||
import database from '../lib/realm';
|
||||
|
||||
const restore = function* restore() {
|
||||
try {
|
||||
|
@ -27,7 +28,8 @@ const restore = function* restore() {
|
|||
]);
|
||||
yield put(actions.appStart('outside'));
|
||||
} else if (server) {
|
||||
yield put(selectServerRequest(server));
|
||||
const serverObj = database.databases.serversDB.objectForPrimaryKey('servers', server);
|
||||
yield put(selectServerRequest(server, serverObj && serverObj.version));
|
||||
}
|
||||
|
||||
yield put(actions.appReady({}));
|
||||
|
|
|
@ -4,8 +4,6 @@ import { takeLatest, put, call } from 'redux-saga/effects';
|
|||
import Navigation from '../lib/Navigation';
|
||||
import { MESSAGES } from '../actions/actionsTypes';
|
||||
import {
|
||||
messagesSuccess,
|
||||
messagesFailure,
|
||||
deleteSuccess,
|
||||
deleteFailure,
|
||||
editSuccess,
|
||||
|
@ -25,19 +23,6 @@ const editMessage = message => RocketChat.editMessage(message);
|
|||
const toggleStarMessage = message => RocketChat.toggleStarMessage(message);
|
||||
const togglePinMessage = message => RocketChat.togglePinMessage(message);
|
||||
|
||||
const get = function* get({ room }) {
|
||||
try {
|
||||
if (room.lastOpen) {
|
||||
yield RocketChat.loadMissedMessages(room);
|
||||
} else {
|
||||
yield RocketChat.loadMessagesForRoom(room);
|
||||
}
|
||||
yield put(messagesSuccess());
|
||||
} catch (err) {
|
||||
yield put(messagesFailure(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteRequest = function* handleDeleteRequest({ message }) {
|
||||
try {
|
||||
yield call(deleteMessage, message);
|
||||
|
@ -97,7 +82,6 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
|||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(MESSAGES.REQUEST, get);
|
||||
yield takeLatest(MESSAGES.DELETE_REQUEST, handleDeleteRequest);
|
||||
yield takeLatest(MESSAGES.EDIT_REQUEST, handleEditRequest);
|
||||
yield takeLatest(MESSAGES.TOGGLE_STAR_REQUEST, handleToggleStarRequest);
|
||||
|
|
|
@ -12,8 +12,31 @@ import database from '../lib/realm';
|
|||
import log from '../utils/log';
|
||||
import I18n from '../i18n';
|
||||
|
||||
const handleSelectServer = function* handleSelectServer({ server }) {
|
||||
const getServerInfo = function* getServerInfo({ server }) {
|
||||
try {
|
||||
const serverInfo = yield RocketChat.getServerInfo(server);
|
||||
if (!serverInfo.success) {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t(serverInfo.message, serverInfo.messageOptions));
|
||||
yield put(serverFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
database.databases.serversDB.write(() => {
|
||||
database.databases.serversDB.create('servers', { id: server, version: serverInfo.version }, true);
|
||||
});
|
||||
|
||||
return serverInfo;
|
||||
} catch (e) {
|
||||
log('getServerInfo', e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
|
||||
try {
|
||||
let serverInfo;
|
||||
if (fetchVersion) {
|
||||
serverInfo = yield getServerInfo({ server });
|
||||
}
|
||||
yield AsyncStorage.setItem('currentServer', server);
|
||||
const userStringified = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
||||
|
||||
|
@ -37,7 +60,7 @@ const handleSelectServer = function* handleSelectServer({ server }) {
|
|||
return result;
|
||||
}, {})));
|
||||
|
||||
yield put(selectServerSuccess(server));
|
||||
yield put(selectServerSuccess(server, fetchVersion ? serverInfo && serverInfo.version : version));
|
||||
} catch (e) {
|
||||
log('handleSelectServer', e);
|
||||
}
|
||||
|
@ -45,13 +68,9 @@ const handleSelectServer = function* handleSelectServer({ server }) {
|
|||
|
||||
const handleServerRequest = function* handleServerRequest({ server }) {
|
||||
try {
|
||||
const result = yield RocketChat.testServer(server);
|
||||
if (!result.success) {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t(result.message, result.messageOptions));
|
||||
yield put(serverFailure());
|
||||
return;
|
||||
}
|
||||
const serverInfo = yield getServerInfo({ server });
|
||||
|
||||
// TODO: cai aqui O.o
|
||||
const loginServicesLength = yield RocketChat.getLoginServices(server);
|
||||
if (loginServicesLength === 0) {
|
||||
Navigation.navigate('LoginView');
|
||||
|
@ -59,10 +78,7 @@ const handleServerRequest = function* handleServerRequest({ server }) {
|
|||
Navigation.navigate('LoginSignupView');
|
||||
}
|
||||
|
||||
database.databases.serversDB.write(() => {
|
||||
database.databases.serversDB.create('servers', { id: server }, true);
|
||||
});
|
||||
yield put(selectServerRequest(server));
|
||||
yield put(selectServerRequest(server, serverInfo.version, false));
|
||||
} catch (e) {
|
||||
yield put(serverFailure());
|
||||
log('handleServerRequest', e);
|
||||
|
|
|
@ -21,6 +21,8 @@ import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
|||
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
user: {
|
||||
|
@ -62,19 +64,20 @@ export default class RoomMembersView extends LoggedView {
|
|||
this.CANCEL_INDEX = 0;
|
||||
this.MUTE_INDEX = 1;
|
||||
this.actionSheetOptions = [''];
|
||||
const { rid, members } = props.navigation.state.params;
|
||||
const { rid } = props.navigation.state.params;
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
this.permissions = RocketChat.hasPermission(['mute-user'], rid);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
isLoading: false,
|
||||
allUsers: false,
|
||||
filtering: false,
|
||||
rid,
|
||||
members,
|
||||
members: [],
|
||||
membersFiltered: [],
|
||||
userLongPressed: {},
|
||||
room: this.rooms[0] || {},
|
||||
options: []
|
||||
options: [],
|
||||
end: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -170,7 +173,9 @@ export default class RoomMembersView extends LoggedView {
|
|||
toggleStatus = () => {
|
||||
try {
|
||||
const { allUsers } = this.state;
|
||||
this.fetchMembers(!allUsers);
|
||||
this.setState({ members: [], allUsers: !allUsers, end: false }, () => {
|
||||
this.fetchMembers();
|
||||
});
|
||||
} catch (e) {
|
||||
log('RoomMembers.toggleStatus', e);
|
||||
}
|
||||
|
@ -186,15 +191,26 @@ export default class RoomMembersView extends LoggedView {
|
|||
});
|
||||
}
|
||||
|
||||
fetchMembers = async(status) => {
|
||||
this.setState({ isLoading: true });
|
||||
const { rid } = this.state;
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
fetchMembers = async() => {
|
||||
const {
|
||||
rid, members, isLoading, allUsers, end
|
||||
} = this.state;
|
||||
const { navigation } = this.props;
|
||||
if (isLoading || end) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
try {
|
||||
const membersResult = await RocketChat.getRoomMembers(rid, status);
|
||||
const members = membersResult.records;
|
||||
this.setState({ allUsers: status, members, isLoading: false });
|
||||
navigation.setParams({ allUsers: status });
|
||||
const membersResult = await RocketChat.getRoomMembers(rid, allUsers, members.length, PAGE_SIZE);
|
||||
const newMembers = membersResult.records;
|
||||
this.setState({
|
||||
members: members.concat(newMembers || []),
|
||||
isLoading: false,
|
||||
end: newMembers.length < PAGE_SIZE
|
||||
});
|
||||
navigation.setParams({ allUsers });
|
||||
} catch (error) {
|
||||
console.log('TCL: fetchMembers -> error', error);
|
||||
this.setState({ isLoading: false });
|
||||
|
@ -260,9 +276,9 @@ export default class RoomMembersView extends LoggedView {
|
|||
const {
|
||||
filtering, members, membersFiltered, isLoading
|
||||
} = this.state;
|
||||
if (isLoading) {
|
||||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
// if (isLoading) {
|
||||
// return <ActivityIndicator style={styles.loading} />;
|
||||
// }
|
||||
return (
|
||||
<SafeAreaView style={styles.list} testID='room-members-view' forceInset={{ bottom: 'never' }}>
|
||||
<StatusBar />
|
||||
|
@ -273,6 +289,16 @@ export default class RoomMembersView extends LoggedView {
|
|||
keyExtractor={item => item._id}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
ListHeaderComponent={this.renderSearchBar}
|
||||
ListFooterComponent={() => {
|
||||
if (isLoading) {
|
||||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
onEndReachedThreshold={0.1}
|
||||
onEndReached={this.fetchMembers}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={10}
|
||||
{...scrollPersistTaps}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
|
|
|
@ -3,23 +3,26 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
View, Text, StyleSheet, ScrollView
|
||||
} from 'react-native';
|
||||
import { emojify } from 'react-emojione';
|
||||
|
||||
import I18n from '../../../i18n';
|
||||
import sharedStyles from '../../Styles';
|
||||
import { isIOS } from '../../../utils/deviceInfo';
|
||||
import { isIOS, isAndroid } from '../../../utils/deviceInfo';
|
||||
import Icon from './Icon';
|
||||
import { COLOR_TEXT_DESCRIPTION, HEADER_TITLE, COLOR_WHITE } from '../../../constants/colors';
|
||||
|
||||
const TITLE_SIZE = 16;
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
height: '100%'
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 6,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
threadContainer: {
|
||||
marginRight: isAndroid ? 20 : undefined
|
||||
},
|
||||
title: {
|
||||
...sharedStyles.textSemibold,
|
||||
color: HEADER_TITLE,
|
||||
|
@ -62,7 +65,7 @@ Typing.propTypes = {
|
|||
};
|
||||
|
||||
const Header = React.memo(({
|
||||
prid, title, type, status, usersTyping, width, height
|
||||
title, type, status, usersTyping, width, height, prid, tmid, widthOffset
|
||||
}) => {
|
||||
const portrait = height > width;
|
||||
let scale = 1;
|
||||
|
@ -72,9 +75,13 @@ const Header = React.memo(({
|
|||
scale = 0.8;
|
||||
}
|
||||
}
|
||||
if (title) {
|
||||
title = emojify(title, { output: 'unicode' });
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.titleContainer}>
|
||||
<View style={[styles.container, { width: width - widthOffset }]}>
|
||||
<View style={[styles.titleContainer, tmid && styles.threadContainer]}>
|
||||
<ScrollView
|
||||
showsHorizontalScrollIndicator={false}
|
||||
horizontal
|
||||
|
@ -82,10 +89,10 @@ const Header = React.memo(({
|
|||
contentContainerStyle={styles.scroll}
|
||||
>
|
||||
<Icon type={prid ? 'discussion' : type} status={status} />
|
||||
<Text style={[styles.title, { fontSize: TITLE_SIZE * scale }]} numberOfLines={1}>{title}</Text>
|
||||
<Text style={[styles.title, { fontSize: TITLE_SIZE * scale }]} numberOfLines={1} testID={`room-view-title-${ title }`}>{title}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<Typing usersTyping={usersTyping} />
|
||||
{type === 'thread' ? null : <Typing usersTyping={usersTyping} />}
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
@ -96,8 +103,10 @@ Header.propTypes = {
|
|||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
prid: PropTypes.string,
|
||||
tmid: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
usersTyping: PropTypes.array
|
||||
usersTyping: PropTypes.array,
|
||||
widthOffset: PropTypes.number
|
||||
};
|
||||
|
||||
Header.defaultProps = {
|
||||
|
|
|
@ -30,6 +30,8 @@ const Icon = React.memo(({ type, status }) => {
|
|||
let icon;
|
||||
if (type === 'discussion') {
|
||||
icon = 'chat';
|
||||
} else if (type === 'thread') {
|
||||
icon = 'thread';
|
||||
} else if (type === 'c') {
|
||||
icon = 'hashtag';
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { CustomHeaderButtons, Item } from '../../../containers/HeaderButton';
|
||||
import database, { safeAddListener } from '../../../lib/realm';
|
||||
import RocketChat from '../../../lib/rocketchat';
|
||||
import log from '../../../utils/log';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
more: {
|
||||
marginHorizontal: 0, marginLeft: 0, marginRight: 5
|
||||
},
|
||||
thread: {
|
||||
marginHorizontal: 0, marginLeft: 0, marginRight: 10
|
||||
}
|
||||
});
|
||||
|
||||
@connect(state => ({
|
||||
userId: state.login.user && state.login.user.id,
|
||||
threadsEnabled: state.settings.Threads_enabled
|
||||
}))
|
||||
class RightButtonsContainer extends React.PureComponent {
|
||||
static propTypes = {
|
||||
userId: PropTypes.string,
|
||||
threadsEnabled: PropTypes.bool,
|
||||
rid: PropTypes.string,
|
||||
t: PropTypes.string,
|
||||
tmid: PropTypes.string,
|
||||
navigation: PropTypes.object
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (props.tmid) {
|
||||
this.thread = database.objectForPrimaryKey('messages', props.tmid);
|
||||
safeAddListener(this.thread, this.updateThread);
|
||||
}
|
||||
this.state = {
|
||||
isFollowingThread: true
|
||||
};
|
||||
}
|
||||
|
||||
updateThread = () => {
|
||||
const { userId } = this.props;
|
||||
this.setState({
|
||||
isFollowingThread: this.thread.replies && !!this.thread.replies.find(t => t === userId)
|
||||
});
|
||||
}
|
||||
|
||||
goThreadsView = () => {
|
||||
const { rid, t, navigation } = this.props;
|
||||
navigation.navigate('ThreadMessagesView', { rid, t });
|
||||
}
|
||||
|
||||
goRoomActionsView = () => {
|
||||
const { rid, t, navigation } = this.props;
|
||||
navigation.navigate('RoomActionsView', { rid, t });
|
||||
}
|
||||
|
||||
toggleFollowThread = async() => {
|
||||
const { isFollowingThread } = this.state;
|
||||
const { tmid } = this.props;
|
||||
try {
|
||||
await RocketChat.toggleFollowMessage(tmid, !isFollowingThread);
|
||||
} catch (e) {
|
||||
console.log('TCL: RightButtonsContainer -> toggleFollowThread -> e', e);
|
||||
log('toggleFollowThread', e);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isFollowingThread } = this.state;
|
||||
const { t, tmid, threadsEnabled } = this.props;
|
||||
if (t === 'l') {
|
||||
return null;
|
||||
}
|
||||
if (tmid) {
|
||||
return (
|
||||
<CustomHeaderButtons>
|
||||
<Item
|
||||
title='bell'
|
||||
iconName={isFollowingThread ? 'Bell-off' : 'bell'}
|
||||
onPress={this.toggleFollowThread}
|
||||
testID={isFollowingThread ? 'room-view-header-unfollow' : 'room-view-header-follow'}
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CustomHeaderButtons>
|
||||
{threadsEnabled ? (
|
||||
<Item
|
||||
title='thread'
|
||||
iconName='thread'
|
||||
onPress={this.goThreadsView}
|
||||
testID='room-view-header-threads'
|
||||
buttonStyle={styles.thread}
|
||||
/>
|
||||
) : null}
|
||||
<Item
|
||||
title='more'
|
||||
iconName='menu'
|
||||
onPress={this.goRoomActionsView}
|
||||
testID='room-view-header-actions'
|
||||
buttonStyle={styles.more}
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RightButtonsContainer;
|
|
@ -6,6 +6,7 @@ import equal from 'deep-equal';
|
|||
|
||||
import database from '../../../lib/realm';
|
||||
import Header from './Header';
|
||||
import RightButtons from './RightButtons';
|
||||
|
||||
@responsive
|
||||
@connect((state, ownProps) => {
|
||||
|
@ -33,9 +34,11 @@ export default class RoomHeaderView extends Component {
|
|||
title: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
prid: PropTypes.string,
|
||||
tmid: PropTypes.string,
|
||||
rid: PropTypes.string,
|
||||
window: PropTypes.object,
|
||||
status: PropTypes.string
|
||||
status: PropTypes.string,
|
||||
widthOffset: PropTypes.number
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -89,19 +92,23 @@ export default class RoomHeaderView extends Component {
|
|||
render() {
|
||||
const { usersTyping } = this.state;
|
||||
const {
|
||||
window, title, type, status, prid
|
||||
window, title, type, status, prid, tmid, widthOffset
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Header
|
||||
prid={prid}
|
||||
tmid={tmid}
|
||||
title={title}
|
||||
type={type}
|
||||
status={status}
|
||||
width={window.width}
|
||||
height={window.height}
|
||||
usersTyping={usersTyping}
|
||||
widthOffset={widthOffset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { RightButtons };
|
||||
|
|
|
@ -1,125 +1,137 @@
|
|||
import React from 'react';
|
||||
import { ActivityIndicator, FlatList, InteractionManager } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { emojify } from 'react-emojione';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import styles from './styles';
|
||||
import database, { safeAddListener } from '../../lib/realm';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import debounce from '../../utils/debounce';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import log from '../../utils/log';
|
||||
import EmptyRoom from './EmptyRoom';
|
||||
// import ScrollBottomButton from './ScrollBottomButton';
|
||||
|
||||
export class List extends React.Component {
|
||||
export class List extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onEndReached: PropTypes.func,
|
||||
renderFooter: PropTypes.func,
|
||||
renderRow: PropTypes.func,
|
||||
rid: PropTypes.string,
|
||||
t: PropTypes.string,
|
||||
window: PropTypes.object
|
||||
tmid: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.time(`${ this.constructor.name } init`);
|
||||
console.time(`${ this.constructor.name } mount`);
|
||||
if (props.tmid) {
|
||||
this.data = database
|
||||
.objects('threadMessages')
|
||||
.filtered('rid = $0', props.tmid)
|
||||
.sorted('ts', true);
|
||||
this.threads = [];
|
||||
} else {
|
||||
this.data = database
|
||||
.objects('messages')
|
||||
.filtered('rid = $0', props.rid)
|
||||
.sorted('ts', true);
|
||||
this.threads = database.objects('threads').filtered('rid = $0', props.rid);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
end: false,
|
||||
messages: this.data.slice()
|
||||
// showScollToBottomButton: false
|
||||
messages: this.data.slice(),
|
||||
threads: this.threads.slice()
|
||||
};
|
||||
|
||||
safeAddListener(this.data, this.updateState);
|
||||
console.timeEnd(`${ this.constructor.name } init`);
|
||||
}
|
||||
|
||||
// shouldComponentUpdate(nextProps, nextState) {
|
||||
// const {
|
||||
// loadingMore, loading, end, showScollToBottomButton, messages
|
||||
// } = this.state;
|
||||
// const { window } = this.props;
|
||||
// return end !== nextState.end
|
||||
// || loadingMore !== nextState.loadingMore
|
||||
// || loading !== nextState.loading
|
||||
// || showScollToBottomButton !== nextState.showScollToBottomButton
|
||||
// // || messages.length !== nextState.messages.length
|
||||
// || !equal(messages, nextState.messages)
|
||||
// || window.width !== nextProps.window.width;
|
||||
// }
|
||||
|
||||
componentDidMount() {
|
||||
console.timeEnd(`${ this.constructor.name } mount`);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.data.removeAllListeners();
|
||||
this.threads.removeAllListeners();
|
||||
if (this.updateState && this.updateState.stop) {
|
||||
this.updateState.stop();
|
||||
}
|
||||
if (this.interactionManager && this.interactionManager.cancel) {
|
||||
this.interactionManager.cancel();
|
||||
if (this.updateThreads && this.updateThreads.stop) {
|
||||
this.updateThreads.stop();
|
||||
}
|
||||
if (this.interactionManagerState && this.interactionManagerState.cancel) {
|
||||
this.interactionManagerState.cancel();
|
||||
}
|
||||
if (this.interactionManagerThreads && this.interactionManagerThreads.cancel) {
|
||||
this.interactionManagerThreads.cancel();
|
||||
}
|
||||
console.countReset(`${ this.constructor.name }.render calls`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateState = debounce(() => {
|
||||
this.interactionManager = InteractionManager.runAfterInteractions(() => {
|
||||
this.setState({ messages: this.data.slice(), loading: false, loadingMore: false });
|
||||
this.interactionManagerState = InteractionManager.runAfterInteractions(() => {
|
||||
this.setState({
|
||||
messages: this.data.slice(),
|
||||
threads: this.threads.slice(),
|
||||
loading: false
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
}, 300, { leading: true });
|
||||
|
||||
onEndReached = async() => {
|
||||
const {
|
||||
loadingMore, loading, end, messages
|
||||
loading, end, messages
|
||||
} = this.state;
|
||||
if (loadingMore || loading || end || messages.length < 50) {
|
||||
if (loading || end || messages.length < 50) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ loadingMore: true });
|
||||
const { rid, t } = this.props;
|
||||
this.setState({ loading: true });
|
||||
const { rid, t, tmid } = this.props;
|
||||
try {
|
||||
const result = await RocketChat.loadMessagesForRoom({ rid, t, latest: this.data[this.data.length - 1].ts });
|
||||
let result;
|
||||
if (tmid) {
|
||||
result = await RocketChat.loadThreadMessages({ tmid, skip: messages.length });
|
||||
} else {
|
||||
result = await RocketChat.loadMessagesForRoom({ rid, t, latest: messages[messages.length - 1].ts });
|
||||
}
|
||||
|
||||
this.setState({ end: result.length < 50 });
|
||||
} catch (e) {
|
||||
this.setState({ loadingMore: false });
|
||||
this.setState({ loading: false });
|
||||
log('ListView.onEndReached', e);
|
||||
}
|
||||
}
|
||||
|
||||
// scrollToBottom = () => {
|
||||
// requestAnimationFrame(() => {
|
||||
// this.list.scrollToOffset({ offset: isNotch ? -90 : -60 });
|
||||
// });
|
||||
// }
|
||||
|
||||
// handleScroll = (event) => {
|
||||
// if (event.nativeEvent.contentOffset.y > 0) {
|
||||
// this.setState({ showScollToBottomButton: true });
|
||||
// } else {
|
||||
// this.setState({ showScollToBottomButton: false });
|
||||
// }
|
||||
// }
|
||||
|
||||
renderFooter = () => {
|
||||
const { loadingMore, loading } = this.state;
|
||||
if (loadingMore || loading) {
|
||||
return <ActivityIndicator style={styles.loadingMore} />;
|
||||
const { loading } = this.state;
|
||||
if (loading) {
|
||||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderItem = ({ item, index }) => {
|
||||
const { messages, threads } = this.state;
|
||||
const { renderRow } = this.props;
|
||||
if (item.tmid) {
|
||||
const thread = threads.find(t => t._id === item.tmid);
|
||||
if (thread) {
|
||||
let tmsg = thread.msg || (thread.attachments && thread.attachments.length && thread.attachments[0].title);
|
||||
tmsg = emojify(tmsg, { output: 'unicode' });
|
||||
item = { ...item, tmsg };
|
||||
}
|
||||
}
|
||||
return renderRow(item, messages[index + 1]);
|
||||
}
|
||||
|
||||
render() {
|
||||
console.count(`${ this.constructor.name }.render calls`);
|
||||
const { renderRow } = this.props;
|
||||
const { messages } = this.state;
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
@ -130,10 +142,9 @@ export class List extends React.Component {
|
|||
keyExtractor={item => item._id}
|
||||
data={messages}
|
||||
extraData={this.state}
|
||||
renderItem={({ item, index }) => renderRow(item, messages[index + 1])}
|
||||
renderItem={this.renderItem}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
style={styles.list}
|
||||
// onScroll={this.handleScroll}
|
||||
inverted
|
||||
removeClippedSubviews
|
||||
initialNumToRender={1}
|
||||
|
@ -144,11 +155,6 @@ export class List extends React.Component {
|
|||
ListFooterComponent={this.renderFooter}
|
||||
{...scrollPersistTaps}
|
||||
/>
|
||||
{/* <ScrollBottomButton
|
||||
show={showScollToBottomButton}
|
||||
onPress={this.scrollToBottom}
|
||||
landscape={window.width > window.height}
|
||||
/> */}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import React from 'react';
|
||||
import { TouchableOpacity, StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { isNotch } from '../../utils/deviceInfo';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { COLOR_BUTTON_PRIMARY } from '../../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
position: 'absolute',
|
||||
width: 42,
|
||||
height: 42,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#EAF2FE',
|
||||
borderRadius: 21,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 1
|
||||
},
|
||||
shadowOpacity: 0.20,
|
||||
shadowRadius: 1.41,
|
||||
elevation: 2
|
||||
}
|
||||
});
|
||||
|
||||
let right;
|
||||
let bottom = 80;
|
||||
if (isNotch) {
|
||||
bottom = 120;
|
||||
}
|
||||
|
||||
const ScrollBottomButton = React.memo(({ show, onPress, landscape }) => {
|
||||
if (show) {
|
||||
if (landscape) {
|
||||
right = 45;
|
||||
} else {
|
||||
right = 30;
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
style={[styles.button, { right, bottom }]}
|
||||
onPress={onPress}
|
||||
>
|
||||
<CustomIcon name='arrow-down' color={COLOR_BUTTON_PRIMARY} size={30} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ScrollBottomButton.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
landscape: PropTypes.bool
|
||||
};
|
||||
export default ScrollBottomButton;
|
|
@ -9,11 +9,11 @@ import { SafeAreaView } from 'react-navigation';
|
|||
import equal from 'deep-equal';
|
||||
import moment from 'moment';
|
||||
import 'react-native-console-time-polyfill';
|
||||
import EJSON from 'ejson';
|
||||
|
||||
import {
|
||||
toggleReactionPicker as toggleReactionPickerAction,
|
||||
actionsShow as actionsShowAction,
|
||||
messagesRequest as messagesRequestAction,
|
||||
editCancel as editCancelAction,
|
||||
replyCancel as replyCancelAction
|
||||
} from '../../actions/messages';
|
||||
|
@ -30,14 +30,15 @@ import UploadProgress from './UploadProgress';
|
|||
import styles from './styles';
|
||||
import log from '../../utils/log';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import I18n from '../../i18n';
|
||||
import ConnectionBadge from '../../containers/ConnectionBadge';
|
||||
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
||||
import RoomHeaderView from './Header';
|
||||
import RoomHeaderView, { RightButtons } from './Header';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import Separator from './Separator';
|
||||
import { COLOR_WHITE } from '../../constants/colors';
|
||||
import debounce from '../../utils/debounce';
|
||||
import buildMessage from '../../lib/methods/helpers/buildMessage';
|
||||
|
||||
@connect(state => ({
|
||||
user: {
|
||||
|
@ -49,13 +50,13 @@ import debounce from '../../utils/debounce';
|
|||
showActions: state.messages.showActions,
|
||||
showErrorActions: state.messages.showErrorActions,
|
||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
|
||||
useRealName: state.settings.UI_Use_Real_Name
|
||||
useRealName: state.settings.UI_Use_Real_Name,
|
||||
isAuthenticated: state.login.isAuthenticated
|
||||
}), dispatch => ({
|
||||
editCancel: () => dispatch(editCancelAction()),
|
||||
replyCancel: () => dispatch(replyCancelAction()),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
||||
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage)),
|
||||
messagesRequest: room => dispatch(messagesRequestAction(room))
|
||||
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomView extends LoggedView {
|
||||
|
@ -64,15 +65,13 @@ export default class RoomView extends LoggedView {
|
|||
const prid = navigation.getParam('prid');
|
||||
const title = navigation.getParam('name');
|
||||
const t = navigation.getParam('t');
|
||||
const tmid = navigation.getParam('tmid');
|
||||
return {
|
||||
headerTitle: <RoomHeaderView rid={rid} prid={prid} title={title} type={t} />,
|
||||
headerRight: t === 'l'
|
||||
? null
|
||||
: (
|
||||
<CustomHeaderButtons>
|
||||
<Item title='more' iconName='menu' onPress={() => navigation.navigate('RoomActionsView', { rid, t })} testID='room-view-header-actions' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
headerTitleContainerStyle: styles.headerTitleContainerStyle,
|
||||
headerTitle: (
|
||||
<RoomHeaderView rid={rid} prid={prid} tmid={tmid} title={title} type={t} widthOffset={tmid ? 95 : 130} />
|
||||
),
|
||||
headerRight: <RightButtons rid={rid} tmid={tmid} t={t} navigation={navigation} />
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -88,9 +87,9 @@ export default class RoomView extends LoggedView {
|
|||
actionMessage: PropTypes.object,
|
||||
appState: PropTypes.string,
|
||||
useRealName: PropTypes.bool,
|
||||
isAuthenticated: PropTypes.bool,
|
||||
toggleReactionPicker: PropTypes.func.isRequired,
|
||||
actionsShow: PropTypes.func,
|
||||
messagesRequest: PropTypes.func,
|
||||
editCancel: PropTypes.func,
|
||||
replyCancel: PropTypes.func
|
||||
};
|
||||
|
@ -101,6 +100,7 @@ export default class RoomView extends LoggedView {
|
|||
console.time(`${ this.constructor.name } mount`);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.t = props.navigation.getParam('t');
|
||||
this.tmid = props.navigation.getParam('tmid');
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||
this.state = {
|
||||
joined: this.rooms.length > 0,
|
||||
|
@ -110,38 +110,37 @@ export default class RoomView extends LoggedView {
|
|||
this.beginAnimating = false;
|
||||
this.beginAnimatingTimeout = setTimeout(() => this.beginAnimating = true, 300);
|
||||
this.messagebox = React.createRef();
|
||||
safeAddListener(this.rooms, this.updateRoom);
|
||||
console.timeEnd(`${ this.constructor.name } init`);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.didMountInteraction = InteractionManager.runAfterInteractions(async() => {
|
||||
this.didMountInteraction = InteractionManager.runAfterInteractions(() => {
|
||||
const { room } = this.state;
|
||||
const { messagesRequest, navigation } = this.props;
|
||||
messagesRequest(room);
|
||||
const { navigation, isAuthenticated } = this.props;
|
||||
|
||||
// if room is joined
|
||||
if (room._id) {
|
||||
if (room._id && !this.tmid) {
|
||||
navigation.setParams({ name: this.getRoomTitle(room), t: room.t });
|
||||
this.sub = await RocketChat.subscribeRoom(room);
|
||||
RocketChat.readMessages(room.rid);
|
||||
if (room.alert || room.unread || room.userMentions) {
|
||||
this.setLastOpen(room.ls);
|
||||
}
|
||||
|
||||
if (isAuthenticated) {
|
||||
this.init();
|
||||
} else {
|
||||
this.setLastOpen(null);
|
||||
EventEmitter.addEventListener('connected', this.handleConnected);
|
||||
}
|
||||
}
|
||||
safeAddListener(this.rooms, this.updateRoom);
|
||||
});
|
||||
console.timeEnd(`${ this.constructor.name } mount`);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
room, joined
|
||||
room, joined, lastOpen
|
||||
} = this.state;
|
||||
const { showActions, showErrorActions, appState } = this.props;
|
||||
|
||||
if (room.ro !== nextState.room.ro) {
|
||||
if (lastOpen !== nextState.lastOpen) {
|
||||
return true;
|
||||
} else if (room.ro !== nextState.room.ro) {
|
||||
return true;
|
||||
} else if (room.f !== nextState.room.f) {
|
||||
return true;
|
||||
|
@ -180,11 +179,13 @@ export default class RoomView extends LoggedView {
|
|||
componentWillUnmount() {
|
||||
if (this.messagebox && this.messagebox.current && this.messagebox.current.text) {
|
||||
const { text } = this.messagebox.current;
|
||||
database.write(() => {
|
||||
const [room] = this.rooms;
|
||||
if (room) {
|
||||
database.write(() => {
|
||||
room.draftMessage = text;
|
||||
});
|
||||
}
|
||||
}
|
||||
this.rooms.removeAllListeners();
|
||||
if (this.sub && this.sub.stop) {
|
||||
this.sub.stop();
|
||||
|
@ -204,9 +205,41 @@ export default class RoomView extends LoggedView {
|
|||
if (this.updateStateInteraction && this.updateStateInteraction.cancel) {
|
||||
this.updateStateInteraction.cancel();
|
||||
}
|
||||
if (this.initInteraction && this.initInteraction.cancel) {
|
||||
this.initInteraction.cancel();
|
||||
}
|
||||
EventEmitter.removeListener('connected', this.handleConnected);
|
||||
console.countReset(`${ this.constructor.name }.render calls`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
init = () => {
|
||||
try {
|
||||
this.initInteraction = InteractionManager.runAfterInteractions(async() => {
|
||||
const { room } = this.state;
|
||||
if (this.tmid) {
|
||||
RocketChat.loadThreadMessages({ tmid: this.tmid, t: this.t });
|
||||
} else {
|
||||
await this.getMessages(room);
|
||||
|
||||
// if room is joined
|
||||
if (room._id) {
|
||||
if (room.alert || room.unread || room.userMentions) {
|
||||
this.setLastOpen(room.ls);
|
||||
} else {
|
||||
this.setLastOpen(null);
|
||||
}
|
||||
RocketChat.readMessages(room.rid).catch(e => console.log(e));
|
||||
this.sub = await RocketChat.subscribeRoom(room);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('TCL: init -> e', e);
|
||||
log('RoomView.init', e);
|
||||
}
|
||||
}
|
||||
|
||||
onMessageLongPress = (message) => {
|
||||
const { actionsShow } = this.props;
|
||||
actionsShow(message);
|
||||
|
@ -232,6 +265,11 @@ export default class RoomView extends LoggedView {
|
|||
});
|
||||
}, 1000, true)
|
||||
|
||||
handleConnected = () => {
|
||||
this.init();
|
||||
EventEmitter.removeListener('connected', this.handleConnected);
|
||||
}
|
||||
|
||||
internalSetState = (...args) => {
|
||||
if (isIOS && this.beginAnimating) {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
|
@ -241,14 +279,16 @@ export default class RoomView extends LoggedView {
|
|||
|
||||
updateRoom = () => {
|
||||
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
|
||||
if (this.rooms[0]) {
|
||||
const room = JSON.parse(JSON.stringify(this.rooms[0] || {}));
|
||||
this.internalSetState({ room });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage = (message) => {
|
||||
sendMessage = (message, tmid) => {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
RocketChat.sendMessage(this.rid, message).then(() => {
|
||||
RocketChat.sendMessage(this.rid, message, this.tmid || tmid).then(() => {
|
||||
this.setLastOpen(null);
|
||||
});
|
||||
};
|
||||
|
@ -258,6 +298,20 @@ export default class RoomView extends LoggedView {
|
|||
return ((room.prid || useRealName) && room.fname) || room.name;
|
||||
}
|
||||
|
||||
getMessages = () => {
|
||||
const { room } = this.state;
|
||||
try {
|
||||
if (room.lastOpen) {
|
||||
return RocketChat.loadMissedMessages(room);
|
||||
} else {
|
||||
return RocketChat.loadMessagesForRoom(room);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('TCL: getMessages -> e', e);
|
||||
log('getMessages', e);
|
||||
}
|
||||
}
|
||||
|
||||
setLastOpen = lastOpen => this.setState({ lastOpen });
|
||||
|
||||
joinRoom = async() => {
|
||||
|
@ -301,9 +355,22 @@ export default class RoomView extends LoggedView {
|
|||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
fetchThreadName = async(tmid) => {
|
||||
try {
|
||||
// TODO: we should build a tmid queue here in order to search for a single tmid only once
|
||||
const thread = await RocketChat.getSingleMessage(tmid);
|
||||
database.write(() => {
|
||||
database.create('threads', buildMessage(EJSON.fromJSONValue(thread)), true);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('TCL: fetchThreadName -> error', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderItem = (item, previousItem) => {
|
||||
const { room, lastOpen } = this.state;
|
||||
const { user } = this.props;
|
||||
const { user, navigation } = this.props;
|
||||
let dateSeparator = null;
|
||||
let showUnreadSeparator = false;
|
||||
|
||||
|
@ -319,23 +386,28 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
if (showUnreadSeparator || dateSeparator) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
const message = (
|
||||
<Message
|
||||
key={item._id}
|
||||
item={item}
|
||||
status={item.status}
|
||||
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
||||
user={user}
|
||||
archived={room.archived}
|
||||
broadcast={room.broadcast}
|
||||
previousItem={previousItem}
|
||||
status={item.status}
|
||||
_updatedAt={item._updatedAt}
|
||||
previousItem={previousItem}
|
||||
navigation={navigation}
|
||||
fetchThreadName={this.fetchThreadName}
|
||||
onReactionPress={this.onReactionPress}
|
||||
onLongPress={this.onMessageLongPress}
|
||||
onDiscussionPress={this.onDiscussionPress}
|
||||
/>
|
||||
);
|
||||
|
||||
if (showUnreadSeparator || dateSeparator) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{message}
|
||||
<Separator
|
||||
ts={dateSeparator}
|
||||
unread={showUnreadSeparator}
|
||||
|
@ -344,28 +416,13 @@ export default class RoomView extends LoggedView {
|
|||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Message
|
||||
key={item._id}
|
||||
item={item}
|
||||
status={item.status}
|
||||
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
||||
user={user}
|
||||
archived={room.archived}
|
||||
broadcast={room.broadcast}
|
||||
previousItem={previousItem}
|
||||
_updatedAt={item._updatedAt}
|
||||
onReactionPress={this.onReactionPress}
|
||||
onLongPress={this.onMessageLongPress}
|
||||
onDiscussionPress={this.onDiscussionPress}
|
||||
/>
|
||||
);
|
||||
return message;
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
const { joined, room } = this.state;
|
||||
|
||||
if (!joined) {
|
||||
if (!joined && !this.tmid) {
|
||||
return (
|
||||
<View style={styles.joinRoomContainer} key='room-view-join' testID='room-view-join'>
|
||||
<Text style={styles.previewMode}>{I18n.t('You_are_in_preview_mode')}</Text>
|
||||
|
@ -397,13 +454,21 @@ export default class RoomView extends LoggedView {
|
|||
return <MessageBox ref={this.messagebox} onSubmit={this.sendMessage} rid={this.rid} roomType={room.t} />;
|
||||
};
|
||||
|
||||
renderList = () => {
|
||||
renderActions = () => {
|
||||
const { room } = this.state;
|
||||
const { rid, t } = room;
|
||||
const {
|
||||
user, showActions, showErrorActions, navigation
|
||||
} = this.props;
|
||||
if (!navigation.isFocused()) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<List rid={rid} t={t} renderRow={this.renderItem} />
|
||||
{this.renderFooter()}
|
||||
{room._id && showActions
|
||||
? <MessageActions room={room} user={user} />
|
||||
: null
|
||||
}
|
||||
{showErrorActions ? <MessageErrorActions /> : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -411,17 +476,14 @@ export default class RoomView extends LoggedView {
|
|||
render() {
|
||||
console.count(`${ this.constructor.name }.render calls`);
|
||||
const { room } = this.state;
|
||||
const { user, showActions, showErrorActions } = this.props;
|
||||
const { rid, t } = room;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} testID='room-view' forceInset={{ bottom: 'never' }}>
|
||||
<StatusBar />
|
||||
{this.renderList()}
|
||||
{room._id && showActions
|
||||
? <MessageActions room={room} user={user} />
|
||||
: null
|
||||
}
|
||||
{showErrorActions ? <MessageErrorActions /> : null}
|
||||
<List rid={rid} t={t} tmid={this.tmid} renderRow={this.renderItem} />
|
||||
{this.renderFooter()}
|
||||
{this.renderActions()}
|
||||
<ReactionPicker onEmojiSelected={this.onReactionPress} />
|
||||
<UploadProgress rid={this.rid} />
|
||||
<ConnectionBadge />
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import {
|
||||
COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE, COLOR_TEXT_DESCRIPTION
|
||||
} from '../../constants/colors';
|
||||
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import sharedStyles from '../Styles';
|
||||
|
||||
export default StyleSheet.create({
|
||||
|
@ -23,8 +24,8 @@ export default StyleSheet.create({
|
|||
height: 1,
|
||||
backgroundColor: COLOR_SEPARATOR
|
||||
},
|
||||
loadingMore: {
|
||||
textAlign: 'center',
|
||||
loading: {
|
||||
flex: 1,
|
||||
padding: 15,
|
||||
color: COLOR_TEXT_DESCRIPTION
|
||||
},
|
||||
|
@ -40,9 +41,6 @@ export default StyleSheet.create({
|
|||
borderRadius: 4,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
loading: {
|
||||
flex: 1
|
||||
},
|
||||
joinRoomContainer: {
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
|
@ -67,5 +65,9 @@ export default StyleSheet.create({
|
|||
fontSize: 16,
|
||||
...sharedStyles.textMedium,
|
||||
...sharedStyles.textColorNormal
|
||||
},
|
||||
headerTitleContainerStyle: {
|
||||
justifyContent: 'flex-start',
|
||||
left: isIOS ? 40 : 50
|
||||
}
|
||||
});
|
||||
|
|
|
@ -264,10 +264,11 @@ export default class RoomsListView extends LoggedView {
|
|||
} = this.props;
|
||||
|
||||
if (server && this.hasActiveDB()) {
|
||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true && t != $0', 'l');
|
||||
if (sortBy === 'alphabetical') {
|
||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('name', false);
|
||||
this.data = this.data.sorted('name', false);
|
||||
} else {
|
||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('roomUpdatedAt', true);
|
||||
this.data = this.data.sorted('roomUpdatedAt', true);
|
||||
}
|
||||
|
||||
let chats = [];
|
||||
|
@ -281,7 +282,7 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
// unread
|
||||
if (showUnread) {
|
||||
this.unread = this.data.filtered('archived != true && open == true').filtered('(unread > 0 || alert == true)');
|
||||
this.unread = this.data.filtered('(unread > 0 || alert == true)');
|
||||
unread = this.removeRealmInstance(this.unread);
|
||||
safeAddListener(this.unread, debounce(() => this.internalSetState({ unread: this.removeRealmInstance(this.unread) }), 300));
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList, View, Text, InteractionManager
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
import EJSON from 'ejson';
|
||||
import moment from 'moment';
|
||||
|
||||
import LoggedView from '../View';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import I18n from '../../i18n';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import database, { safeAddListener } from '../../lib/realm';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import buildMessage from '../../lib/methods/helpers/buildMessage';
|
||||
import log from '../../utils/log';
|
||||
import debounce from '../../utils/debounce';
|
||||
|
||||
const Separator = React.memo(() => <View style={styles.separator} />);
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
customEmojis: state.customEmojis,
|
||||
user: {
|
||||
id: state.login.user && state.login.user.id,
|
||||
username: state.login.user && state.login.user.username,
|
||||
token: state.login.user && state.login.user.token
|
||||
}
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class ThreadMessagesView extends LoggedView {
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Threads')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
user: PropTypes.object,
|
||||
navigation: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('ThreadMessagesView', props);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.t = props.navigation.getParam('t');
|
||||
this.messages = database.objects('threads').filtered('rid = $0', this.rid);
|
||||
safeAddListener(this.messages, this.updateMessages);
|
||||
this.state = {
|
||||
loading: false,
|
||||
messages: this.messages.slice(),
|
||||
end: false,
|
||||
total: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.load();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { loading, messages, end } = this.state;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.messages, messages)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.end, end)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
updateMessages = () => {
|
||||
this.setState({ messages: this.messages.slice() });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
load = debounce(async() => {
|
||||
const {
|
||||
loading, end, total
|
||||
} = this.state;
|
||||
if (end || loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ loading: true });
|
||||
|
||||
try {
|
||||
const result = await RocketChat.getThreadsList({ rid: this.rid, limit: 50, skip: total });
|
||||
|
||||
database.write(() => result.forEach((message) => {
|
||||
try {
|
||||
database.create('threads', buildMessage(EJSON.fromJSONValue(message)), true);
|
||||
} catch (e) {
|
||||
log('ThreadMessagesView -> load -> create', e);
|
||||
}
|
||||
}));
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.setState(prevState => ({
|
||||
loading: false,
|
||||
end: result.length < 50,
|
||||
total: prevState.total + result.length
|
||||
}));
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('ThreadMessagesView -> catch -> error', error);
|
||||
this.setState({ loading: false, end: true });
|
||||
}
|
||||
}, 300, true)
|
||||
|
||||
formatMessage = lm => (
|
||||
lm ? moment(lm).calendar(null, {
|
||||
lastDay: `[${ I18n.t('Yesterday') }]`,
|
||||
sameDay: 'h:mm A',
|
||||
lastWeek: 'dddd',
|
||||
sameElse: 'MMM D'
|
||||
}) : null
|
||||
)
|
||||
|
||||
renderSeparator = () => <Separator />
|
||||
|
||||
renderEmpty = () => (
|
||||
<View style={styles.listEmptyContainer} testID='thread-messages-view'>
|
||||
<Text style={styles.noDataFound}>{I18n.t('No_thread_messages')}</Text>
|
||||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const { user, navigation } = this.props;
|
||||
return (
|
||||
<Message
|
||||
key={item._id}
|
||||
item={item}
|
||||
user={user}
|
||||
archived={false}
|
||||
broadcast={false}
|
||||
status={item.status}
|
||||
_updatedAt={item._updatedAt}
|
||||
navigation={navigation}
|
||||
customTimeFormat='MMM D'
|
||||
customThreadTimeFormat='MMM Do YYYY, h:mm:ss a'
|
||||
fetchThreadName={this.fetchThreadName}
|
||||
onDiscussionPress={this.onDiscussionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { messages, loading } = this.state;
|
||||
|
||||
if (!loading && messages.length === 0) {
|
||||
return this.renderEmpty();
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.list} testID='thread-messages-view' forceInset={{ bottom: 'never' }}>
|
||||
<StatusBar />
|
||||
<FlatList
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
keyExtractor={item => item._id}
|
||||
onEndReached={this.load}
|
||||
onEndReachedThreshold={0.5}
|
||||
maxToRenderPerBatch={5}
|
||||
initialNumToRender={1}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
ListFooterComponent={loading ? <RCActivityIndicator /> : null}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../Styles';
|
||||
import { COLOR_WHITE, COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
list: {
|
||||
flex: 1,
|
||||
backgroundColor: COLOR_WHITE
|
||||
},
|
||||
listEmptyContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: COLOR_WHITE
|
||||
},
|
||||
noDataFound: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textRegular,
|
||||
...sharedStyles.textColorNormal
|
||||
},
|
||||
contentContainer: {
|
||||
paddingBottom: 30
|
||||
},
|
||||
separator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
width: '100%',
|
||||
marginLeft: 60,
|
||||
marginTop: 10,
|
||||
backgroundColor: COLOR_SEPARATOR
|
||||
}
|
||||
});
|
|
@ -21,6 +21,8 @@ async function navigateToRoom() {
|
|||
}
|
||||
|
||||
describe('Room screen', () => {
|
||||
const mainRoom = `private${ data.random }`;
|
||||
|
||||
before(async() => {
|
||||
await navigateToRoom();
|
||||
});
|
||||
|
@ -28,6 +30,8 @@ describe('Room screen', () => {
|
|||
describe('Render', async() => {
|
||||
it('should have room screen', async() => {
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
await waitFor(element(by.id(`room-view-title-${ mainRoom }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ mainRoom }`))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have messages list', async() => {
|
||||
|
@ -228,17 +232,6 @@ describe('Room screen', () => {
|
|||
await expect(element(by.id('message-reaction-:grinning:'))).toBeNotVisible();
|
||||
});
|
||||
|
||||
it('should reply message', async() => {
|
||||
await mockMessage('reply');
|
||||
await element(by.text(`${ data.random }reply`)).longPress();
|
||||
await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.text('Message actions'))).toBeVisible();
|
||||
await element(by.text('Reply')).tap();
|
||||
await element(by.id('messagebox-input')).typeText('replied');
|
||||
await element(by.id('messagebox-send-message')).tap();
|
||||
// TODO: test if reply was sent
|
||||
});
|
||||
|
||||
it('should edit message', async() => {
|
||||
await mockMessage('edit');
|
||||
await element(by.text(`${ data.random }edit`)).longPress();
|
||||
|
@ -281,6 +274,67 @@ describe('Room screen', () => {
|
|||
// TODO: delete message - swipe on action sheet missing
|
||||
});
|
||||
|
||||
describe('Thread', async() => {
|
||||
const thread = `${ data.random }thread`;
|
||||
it('should create thread', async() => {
|
||||
await mockMessage('thread');
|
||||
await element(by.text(thread)).longPress();
|
||||
await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.text('Message actions'))).toBeVisible();
|
||||
await element(by.text('Reply')).tap();
|
||||
await element(by.id('messagebox-input')).typeText('replied');
|
||||
await element(by.id('messagebox-send-message')).tap();
|
||||
await waitFor(element(by.id(`message-thread-button-${ thread }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`message-thread-button-${ thread }`))).toBeVisible();
|
||||
await waitFor(element(by.id(`message-thread-replied-on-${ thread }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`message-thread-replied-on-${ thread }`))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should navigate to thread from button', async() => {
|
||||
await element(by.id(`message-thread-button-${ thread }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
await waitFor(element(by.id(`room-view-title-${ thread }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ thread }`))).toBeVisible();
|
||||
await tapBack();
|
||||
});
|
||||
|
||||
it('should toggle follow thread', async() => {
|
||||
await element(by.id(`message-thread-button-${ thread }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
await waitFor(element(by.id(`room-view-title-${ thread }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ thread }`))).toBeVisible();
|
||||
await element(by.id('room-view-header-unfollow')).tap();
|
||||
await waitFor(element(by.id('room-view-header-follow'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view-header-follow'))).toBeVisible();
|
||||
await element(by.id('room-view-header-follow')).tap();
|
||||
await waitFor(element(by.id('room-view-header-unfollow'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view-header-unfollow'))).toBeVisible();
|
||||
await tapBack();
|
||||
});
|
||||
|
||||
it('should navigate to thread from thread name', async() => {
|
||||
await element(by.id(`message-thread-replied-on-${ thread }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
await waitFor(element(by.id(`room-view-title-${ thread }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ thread }`))).toBeVisible();
|
||||
await tapBack();
|
||||
});
|
||||
|
||||
it('should navigate to thread from threads view', async() => {
|
||||
await element(by.id('room-view-header-threads')).tap();
|
||||
await waitFor(element(by.id('thread-messages-view'))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id('thread-messages-view'))).toBeVisible();
|
||||
await element(by.id(`message-thread-button-${ thread }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
await waitFor(element(by.id(`room-view-title-${ thread }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ thread }`))).toBeVisible();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('thread-messages-view'))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id('thread-messages-view'))).toBeVisible();
|
||||
await tapBack();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async() => {
|
||||
takeScreenshot();
|
||||
});
|
||||
|
|
|
@ -44,10 +44,6 @@ describe('Join public room', () => {
|
|||
|
||||
// Render - Header
|
||||
describe('Header', async() => {
|
||||
it('should have star button', async() => {
|
||||
await expect(element(by.id('room-view-header-star'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have actions button ', async() => {
|
||||
await expect(element(by.id('room-view-header-actions'))).toBeVisible();
|
||||
});
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
"react-navigation-header-buttons": "^2.1.2",
|
||||
"react-redux": "^6.0.0",
|
||||
"reactotron-react-native": "2.2",
|
||||
"realm": "2.24",
|
||||
"realm": "2.26.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-enhancer-react-native-appstate": "^0.3.1",
|
||||
"redux-immutable-state-invariant": "^2.1.0",
|
||||
|
|
|
@ -26,6 +26,7 @@ const author = {
|
|||
const baseUrl = 'https://open.rocket.chat';
|
||||
const customEmojis = { react_rocket: 'png', nyan_rocket: 'png', marioparty: 'gif' };
|
||||
const date = new Date(2017, 10, 10, 10);
|
||||
const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
|
||||
|
||||
const Message = props => (
|
||||
<MessageComponent
|
||||
|
@ -50,7 +51,7 @@ export default (
|
|||
<Message msg='Message' />
|
||||
|
||||
<Separator title='Long message' />
|
||||
<Message msg='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum' />
|
||||
<Message msg={longText} />
|
||||
|
||||
<Separator title='Grouped messages' />
|
||||
<Message msg='...' />
|
||||
|
@ -58,7 +59,7 @@ export default (
|
|||
msg='Different user'
|
||||
author={{
|
||||
...author,
|
||||
username: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'
|
||||
username: longText
|
||||
}}
|
||||
/>
|
||||
<Message msg='This is the third message' header={false} />
|
||||
|
@ -74,7 +75,7 @@ export default (
|
|||
msg='Message'
|
||||
author={{
|
||||
...author,
|
||||
username: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'
|
||||
username: longText
|
||||
}}
|
||||
alias='Diego Mello'
|
||||
/>
|
||||
|
@ -262,6 +263,7 @@ export default (
|
|||
header={false}
|
||||
/>
|
||||
|
||||
{/* Legacy thread */}
|
||||
<Separator title='Message with reply' />
|
||||
<Message
|
||||
msg="I'm fine!"
|
||||
|
@ -282,6 +284,127 @@ export default (
|
|||
}]}
|
||||
/>
|
||||
|
||||
<Separator title='Message with thread' />
|
||||
<Message
|
||||
msg='How are you?'
|
||||
tcount={1}
|
||||
tlm={date}
|
||||
/>
|
||||
<Message
|
||||
msg="I'm fine!"
|
||||
tmid='1'
|
||||
tmsg='How are you?'
|
||||
/>
|
||||
<Message
|
||||
msg="I'm fine!"
|
||||
tmid='1'
|
||||
tmsg='Thread with emoji :) :joy:'
|
||||
/>
|
||||
<Message
|
||||
msg="I'm fine!"
|
||||
tmid='1'
|
||||
tmsg={longText}
|
||||
/>
|
||||
<Message
|
||||
msg={longText}
|
||||
tmid='1'
|
||||
tmsg='How are you?'
|
||||
/>
|
||||
<Message
|
||||
msg={longText}
|
||||
tmid='1'
|
||||
tmsg={longText}
|
||||
/>
|
||||
<Message
|
||||
msg='How are you?'
|
||||
tcount={0}
|
||||
tlm={date}
|
||||
/>
|
||||
<Message
|
||||
msg='How are you?'
|
||||
tcount={9999}
|
||||
tlm={date}
|
||||
/>
|
||||
{/* <Message
|
||||
msg='How are you?'
|
||||
tcount={9999}
|
||||
tlm={moment().subtract(1, 'hour')}
|
||||
/>
|
||||
<Message
|
||||
msg='How are you?'
|
||||
tcount={9999}
|
||||
tlm={moment().subtract(1, 'day')}
|
||||
/>
|
||||
<Message
|
||||
msg='How are you?'
|
||||
tcount={9999}
|
||||
tlm={moment().subtract(5, 'day')}
|
||||
/>
|
||||
<Message
|
||||
msg='How are you?'
|
||||
tcount={9999}
|
||||
tlm={moment().subtract(30, 'day')}
|
||||
/> */}
|
||||
|
||||
<Separator title='Discussion' />
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={null}
|
||||
dlm={null}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1}
|
||||
dlm={date}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={10}
|
||||
dlm={date}
|
||||
msg={longText}
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={date}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
{/* <Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(1, 'hour')}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(1, 'day')}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(5, 'day')}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(30, 'day')}
|
||||
msg='This is a discussion'
|
||||
/> */}
|
||||
|
||||
|
||||
<Separator title='URL' />
|
||||
<Message
|
||||
urls={[{
|
||||
|
@ -360,64 +483,6 @@ export default (
|
|||
<Separator title='Broadcast' />
|
||||
<Message msg='Broadcasted message' broadcast replyBroadcast={() => alert('broadcast!')} />
|
||||
|
||||
<Separator title='Discussion' />
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={null}
|
||||
dlm={null}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1}
|
||||
dlm={date}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={10}
|
||||
dlm={date}
|
||||
msg='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={date}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
{/* <Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(1, 'hour')}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(1, 'day')}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(5, 'day')}
|
||||
msg='This is a discussion'
|
||||
/>
|
||||
<Message
|
||||
type='discussion-created'
|
||||
drid='aisduhasidhs'
|
||||
dcount={1000}
|
||||
dlm={moment().subtract(30, 'day')}
|
||||
msg='This is a discussion'
|
||||
/> */}
|
||||
|
||||
<Separator title='Archived' />
|
||||
<Message msg='This message is inside an archived room' archived />
|
||||
|
||||
|
|
|
@ -31,6 +31,9 @@ const Header = props => (
|
|||
height={480}
|
||||
{...props}
|
||||
/>
|
||||
<CustomHeaderButtons>
|
||||
<Item title='thread' iconName='thread' />
|
||||
</CustomHeaderButtons>
|
||||
<CustomHeaderButtons>
|
||||
<Item title='more' iconName='menu' />
|
||||
</CustomHeaderButtons>
|
||||
|
@ -47,6 +50,7 @@ export default (
|
|||
<Header type='c' />
|
||||
<Header type='p' />
|
||||
<Header type='discussion' />
|
||||
<Header type='thread' />
|
||||
|
||||
<StoriesSeparator title='Typing' />
|
||||
<Header usersTyping={[{ username: 'diego.mello' }]} />
|
||||
|
|
260
yarn.lock
260
yarn.lock
|
@ -1878,6 +1878,20 @@ arr-union@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
|
||||
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
|
||||
|
||||
array-back@^1.0.3, array-back@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b"
|
||||
integrity sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=
|
||||
dependencies:
|
||||
typical "^2.6.0"
|
||||
|
||||
array-back@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022"
|
||||
integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==
|
||||
dependencies:
|
||||
typical "^2.6.1"
|
||||
|
||||
array-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
|
||||
|
@ -3080,7 +3094,7 @@ binstring@^0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/binstring/-/binstring-0.2.1.tgz#8a174d301f6d54efda550dd98bb4cb524eacd75d"
|
||||
integrity sha1-ihdNMB9tVO/aVQ3Zi7TLUk6s110=
|
||||
|
||||
bl@^1.2.1:
|
||||
bl@^1.0.0, bl@^1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
||||
|
@ -3306,11 +3320,29 @@ bser@^2.0.0:
|
|||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
buffer-crc32@^0.2.13:
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
|
||||
|
||||
buffer-alloc@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
|
@ -3330,6 +3362,14 @@ buffer@^4.3.0:
|
|||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6"
|
||||
integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
builtin-modules@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
|
@ -3723,6 +3763,15 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
command-line-args@^4.0.6:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-4.0.7.tgz#f8d1916ecb90e9e121eda6428e41300bfb64cc46"
|
||||
integrity sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA==
|
||||
dependencies:
|
||||
array-back "^2.0.0"
|
||||
find-replace "^1.0.3"
|
||||
typical "^2.6.1"
|
||||
|
||||
commander@2.15.1:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
|
||||
|
@ -3743,6 +3792,13 @@ commander@~2.13.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
|
||||
integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==
|
||||
|
||||
commander@~2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
commist@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/commist/-/commist-1.0.0.tgz#c0c352501cf6f52e9124e3ef89c9806e2022ebef"
|
||||
|
@ -4201,6 +4257,59 @@ decode-uri-component@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||
|
||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
|
||||
dependencies:
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
tar-stream "^1.5.2"
|
||||
|
||||
decompress-tarbz2@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
|
||||
dependencies:
|
||||
decompress-tar "^4.1.0"
|
||||
file-type "^6.1.0"
|
||||
is-stream "^1.1.0"
|
||||
seek-bzip "^1.0.5"
|
||||
unbzip2-stream "^1.0.9"
|
||||
|
||||
decompress-targz@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
|
||||
dependencies:
|
||||
decompress-tar "^4.1.1"
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
|
||||
decompress-unzip@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||
integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
|
||||
dependencies:
|
||||
file-type "^3.8.0"
|
||||
get-stream "^2.2.0"
|
||||
pify "^2.3.0"
|
||||
yauzl "^2.4.2"
|
||||
|
||||
decompress@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
||||
integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=
|
||||
dependencies:
|
||||
decompress-tar "^4.0.0"
|
||||
decompress-tarbz2 "^4.0.0"
|
||||
decompress-targz "^4.0.0"
|
||||
decompress-unzip "^4.0.1"
|
||||
graceful-fs "^4.1.10"
|
||||
make-dir "^1.0.0"
|
||||
pify "^2.3.0"
|
||||
strip-dirs "^2.0.0"
|
||||
|
||||
deep-equal@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||
|
@ -5334,6 +5443,13 @@ fbjs@^1.0.0:
|
|||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.18"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
figgy-pudding@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
|
||||
|
@ -5379,6 +5495,21 @@ file-system-cache@^1.0.5:
|
|||
fs-extra "^0.30.0"
|
||||
ramda "^0.21.0"
|
||||
|
||||
file-type@^3.8.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
|
||||
|
||||
file-type@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||
integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
|
||||
|
||||
file-type@^6.1.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
|
||||
|
||||
file-uri-to-path@1:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
|
@ -5467,6 +5598,14 @@ find-cache-dir@^2.0.0:
|
|||
make-dir "^1.0.0"
|
||||
pkg-dir "^3.0.0"
|
||||
|
||||
find-replace@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0"
|
||||
integrity sha1-uI5zZNLZyVlVnziMZmcNYTBEH6A=
|
||||
dependencies:
|
||||
array-back "^1.0.4"
|
||||
test-value "^2.1.0"
|
||||
|
||||
find-up@3.0.0, find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
|
@ -5577,6 +5716,11 @@ from2@^2.1.0:
|
|||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.0"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-extra@^0.30.0:
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
|
||||
|
@ -5597,7 +5741,7 @@ fs-extra@^1.0.0:
|
|||
jsonfile "^2.1.0"
|
||||
klaw "^1.0.0"
|
||||
|
||||
fs-extra@^4.0.2:
|
||||
fs-extra@^4.0.2, fs-extra@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
|
||||
integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
|
||||
|
@ -5714,6 +5858,14 @@ get-port@^2.1.0:
|
|||
dependencies:
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
get-stream@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||
integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
|
@ -5928,11 +6080,16 @@ got@^6.7.1:
|
|||
unzip-response "^2.0.1"
|
||||
url-parse-lax "^1.0.0"
|
||||
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
|
||||
graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||
|
||||
"graceful-readlink@>= 1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
||||
|
||||
graphlib@^2.1.1, graphlib@^2.1.5:
|
||||
version "2.1.7"
|
||||
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.7.tgz#b6a69f9f44bd9de3963ce6804a2fc9e73d86aecc"
|
||||
|
@ -6423,7 +6580,7 @@ inherits@2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
|
||||
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
|
||||
|
||||
ini@^1.3.0, ini@^1.3.4, ini@~1.3.0:
|
||||
ini@^1.3.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
|
@ -6720,6 +6877,11 @@ is-installed-globally@^0.1.0:
|
|||
global-dirs "^0.1.0"
|
||||
is-path-inside "^1.0.0"
|
||||
|
||||
is-natural-number@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
|
||||
|
||||
is-negated-glob@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
|
||||
|
@ -9425,12 +9587,17 @@ pbkdf2@^3.0.3:
|
|||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
|
||||
pify@^2.0.0:
|
||||
pify@^2.0.0, pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
||||
|
@ -9680,7 +9847,7 @@ process@~0.5.1:
|
|||
resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
|
||||
integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=
|
||||
|
||||
progress@^2.0.0:
|
||||
progress@^2.0.0, progress@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||
|
@ -10646,7 +10813,7 @@ read-pkg@^3.0.0:
|
|||
normalize-package-data "^2.3.2"
|
||||
path-type "^3.0.0"
|
||||
|
||||
"readable-stream@1 || 2", readable-stream@2, "readable-stream@> 1.0.0 < 3.0.0", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
"readable-stream@1 || 2", readable-stream@2, "readable-stream@> 1.0.0 < 3.0.0", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
||||
|
@ -10709,16 +10876,22 @@ readdirp@^2.0.0:
|
|||
micromatch "^3.1.10"
|
||||
readable-stream "^2.0.2"
|
||||
|
||||
realm@2.24:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/realm/-/realm-2.24.0.tgz#4c804bed23360b7d4f23964e708d142608f7a335"
|
||||
integrity sha512-pIeZNoUfqrfo9WRdP3PtVVjh2aEde9l6T5tReN4as9MLn9sC/17tppatrl5S3gfakxvNQH3uJ9FdYI7lS6EspQ==
|
||||
realm@2.26.1:
|
||||
version "2.26.1"
|
||||
resolved "https://registry.yarnpkg.com/realm/-/realm-2.26.1.tgz#9d890c85c4d0946bef0a3ece736551c6a8a5dc49"
|
||||
integrity sha512-kkDOMV5vgaPOYgTELHFPws9suEF0LI/kSb8SIZ615STKHLHLiRxioxgBcu5beO5HVkjxe5jYx7duSB3NASr+AA==
|
||||
dependencies:
|
||||
command-line-args "^4.0.6"
|
||||
decompress "^4.2.0"
|
||||
deepmerge "2.1.0"
|
||||
fs-extra "^4.0.3"
|
||||
https-proxy-agent "^2.2.1"
|
||||
ini "^1.3.5"
|
||||
nan "^2.12.1"
|
||||
node-fetch "^1.7.3"
|
||||
node-machine-id "^1.1.10"
|
||||
node-pre-gyp "^0.11.0"
|
||||
progress "^2.0.3"
|
||||
prop-types "^15.6.2"
|
||||
request "^2.88.0"
|
||||
stream-counter "^1.0.0"
|
||||
|
@ -11288,6 +11461,13 @@ secure-keys@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca"
|
||||
integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=
|
||||
|
||||
seek-bzip@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||
integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=
|
||||
dependencies:
|
||||
commander "~2.8.1"
|
||||
|
||||
semver-diff@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||
|
@ -12087,6 +12267,13 @@ strip-bom@^2.0.0:
|
|||
dependencies:
|
||||
is-utf8 "^0.2.0"
|
||||
|
||||
strip-dirs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
|
||||
dependencies:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
strip-eof@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
|
@ -12192,6 +12379,19 @@ tapable@^1.0.0, tapable@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e"
|
||||
integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.2.0"
|
||||
end-of-stream "^1.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.1"
|
||||
xtend "^4.0.0"
|
||||
|
||||
tar@^4:
|
||||
version "4.4.8"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
|
||||
|
@ -12274,6 +12474,14 @@ test-exclude@^4.2.1:
|
|||
read-pkg-up "^1.0.1"
|
||||
require-main-filename "^1.0.1"
|
||||
|
||||
test-value@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291"
|
||||
integrity sha1-Edpv9nDzRxpztiXKTz/c97t0gpE=
|
||||
dependencies:
|
||||
array-back "^1.0.3"
|
||||
typical "^2.6.0"
|
||||
|
||||
text-table@0.2.0, text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
|
@ -12324,7 +12532,7 @@ through2@^2.0.0, through2@^2.0.1, through2@^2.0.2, through2@~2.0.0:
|
|||
readable-stream "~2.3.6"
|
||||
xtend "~4.0.1"
|
||||
|
||||
through@^2.3.6:
|
||||
through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
@ -12381,6 +12589,11 @@ to-arraybuffer@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
||||
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
|
||||
|
||||
to-buffer@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
|
||||
|
||||
to-fast-properties@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
|
||||
|
@ -12514,6 +12727,11 @@ typedarray@^0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typical@^2.6.0, typical@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d"
|
||||
integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.19"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
|
||||
|
@ -12557,6 +12775,14 @@ ultron@~1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
|
||||
integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
|
||||
|
||||
unbzip2-stream@^1.0.9:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz#d156d205e670d8d8c393e1c02ebd506422873f6a"
|
||||
integrity sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==
|
||||
dependencies:
|
||||
buffer "^5.2.1"
|
||||
through "^2.3.8"
|
||||
|
||||
unc-path-regex@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
|
@ -13345,3 +13571,11 @@ yargs@^9.0.0:
|
|||
which-module "^2.0.0"
|
||||
y18n "^3.2.1"
|
||||
yargs-parser "^7.0.0"
|
||||
|
||||
yauzl@^2.4.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
|
Loading…
Reference in New Issue