Skip to content
This repository has been archived by the owner on Oct 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3394 from withspectrum/2.4.13
Browse files Browse the repository at this point in the history
2.4.13
  • Loading branch information
brianlovin authored Jun 26, 2018
2 parents 4134b69 + 6b888cd commit 69052f0
Show file tree
Hide file tree
Showing 21 changed files with 579 additions and 473 deletions.
2 changes: 2 additions & 0 deletions api/queries/directMessageThread/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// @flow
import directMessageThread from './rootDirectMessageThread';
import directMessageThreadByUserId from './rootDirectMessageThreadByUserId';
import messageConnection from './messageConnection';
import participants from './participants';
import snippet from './snippet';

module.exports = {
Query: {
directMessageThread,
directMessageThreadByUserId,
},
DirectMessageThread: {
messageConnection,
Expand Down
21 changes: 21 additions & 0 deletions api/queries/directMessageThread/rootDirectMessageThreadByUserId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @flow
import type { GraphQLContext } from '../../';
import { checkForExistingDMThread } from '../../models/directMessageThread';
import { isAuthedResolver as requireAuth } from '../../utils/permissions';

type Args = {
userId: string,
};

export default requireAuth(async (_: any, args: Args, ctx: GraphQLContext) => {
// signed out users will never be able to view a dm thread
const { user: currentUser, loaders } = ctx;
const { userId } = args;

const allMemberIds = [userId, currentUser.id];
const existingThread = await checkForExistingDMThread(allMemberIds);

if (!existingThread) return null;

return loaders.directMessageThread.load(existingThread);
});
1 change: 1 addition & 0 deletions api/types/DirectMessageThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const DirectMessageThread = /* GraphQL */ `
extend type Query {
directMessageThread(id: ID!): DirectMessageThread
directMessageThreadByUserId(userId: ID!): DirectMessageThread
}
enum MessageType {
Expand Down
11 changes: 9 additions & 2 deletions config-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,13 @@ module.exports = function override(config, env) {
});
config.plugins.push(
new OfflinePlugin({
appShell: '/index.html',
caches: process.env.NODE_ENV === 'development' ? {} : 'all',
externals,
autoUpdate: true,
// NOTE(@mxstbr): Normally this is handled by setting
// appShell: './index.html'
// but we don't want to serve the app shell for the `/api` and `/auth` routes
// which means we have to manually do this and filter any of those routes out
cacheMaps: [
{
match: function(url) {
Expand All @@ -118,8 +121,12 @@ module.exports = function override(config, env) {
})
)
return false;
return url;
// This function will be stringified and injected into the ServiceWorker on the client, where
// location will be a thing
// eslint-disable-next-line no-restricted-globals
return new URL('./index.html', location);
},
requestTypes: ['navigate'],
},
],
rewrites: arg => arg,
Expand Down
2 changes: 1 addition & 1 deletion mobile/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Toasts from './components/Toasts';
import theme from '../shared/theme';
import { createClient } from '../shared/graphql';
import Login from './views/Login';
import TabBar from './views/TabBar';
import TabBar from './views/TabBar/App';
import { SetUsername, ExploreCommunities } from './views/UserOnboarding';
import { authenticate } from './actions/authentication';

Expand Down
8 changes: 6 additions & 2 deletions mobile/components/Lists/ThreadListItem/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ class ThreadListItemHandlers extends Component<Props> {
const canModerateCommunity = isCommunityModerator || isCommunityOwner;

let options, cancelButtonIndex, destructiveButtonIndex;
options = [SHARE, MESSAGE_AUTHOR];
options = [SHARE];

if (thread.author.user.id !== currentUser.id) {
options = options.concat(MESSAGE_AUTHOR);
}

options = options.concat(
thread.receiveNotifications ? MUTE_CONVERSATION : SUBSCRIBE_CONVERSATION
Expand Down Expand Up @@ -211,7 +215,7 @@ class ThreadListItemHandlers extends Component<Props> {
routeName: 'DirectMessageComposer',
key: thread.author.user.id,
params: {
presetUserIds: [thread.author.user.id],
presetUserId: thread.author.user.id,
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion mobile/components/Toasts/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const Container = styled.View`
left: 0;
right: 0;
width: 100%;
height: 40px;
height: 48px;
z-index: 1;
overflow: hidden;
`;
Expand Down
216 changes: 216 additions & 0 deletions mobile/views/DirectMessageComposer/Composer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// @flow
import React from 'react';
import compose from 'recompose/compose';
import type { NavigationProps } from 'react-navigation';
import { withCurrentUser } from '../../components/WithCurrentUser';
import { throttle, debounce } from 'throttle-debounce';
import withSafeView from '../../components/SafeAreaView';
import ChatInput from '../../components/ChatInput';
import PeopleSearchView from '../Search/PeopleSearchView';
import getCurrentUserDMThreadConnection from '../../../shared/graphql/queries/directMessageThread/getCurrentUserDMThreadConnection';
import createDirectMessageThread, {
type CreateDirectMessageThreadType,
type CreateDirectMessageThreadProps,
} from '../../../shared/graphql/mutations/directMessageThread/createDirectMessageThread';
import type { GetUserType } from '../../../shared/graphql/queries/user/getUser';
import {
ComposerWrapper,
SearchInputArea,
SelectedUsers,
UserSearchInput,
} from './style';
import { events, track } from '../../utils/analytics';
import { FullscreenNullState } from '../../components/NullStates';
import SelectedUser from './SelectedUser';

type Props = {
...$Exact<NavigationProps>,
...$Exact<CreateDirectMessageThreadProps>,
currentUser: GetUserType,
presetUserId?: string,
};

type State = {
wipSearchString: ?string,
searchString: ?string,
selectedUsers: string[],
};

class DirectMessageComposer extends React.Component<Props, State> {
state = {
wipSearchString: '',
searchString: '',
selectedUsers: [],
};

searchInput: ?{
focus: () => void,
};

constructor() {
super();
this.searchDebounced = debounce(300, this.searchDebounced);
this.searchThrottled = throttle(300, this.searchThrottled);
}

componentDidMount() {
track(events.DIRECT_MESSAGE_THREAD_COMPOSER_VIEWED);

const { presetUserId } = this.props;

if (presetUserId) {
this.setState({
selectedUsers: [presetUserId],
});
}
}

onChangeText = (text: string) => {
this.setState({
wipSearchString: text,
});
};

onChangeText = (text: string) => {
this.setState({ wipSearchString: text }, () => {
const { wipSearchString } = this.state;
if (wipSearchString && wipSearchString.length < 5) {
this.searchThrottled(wipSearchString);
} else {
this.searchDebounced(wipSearchString);
}
});
};

onFinishTyping = (e: { nativeEvent: { text: string } }) => {
this.search(e.nativeEvent.text);
};

searchDebounced = (searchString: ?string) => {
this.setState({
searchString,
});
};

searchThrottled = (searchString: ?string) => {
this.setState({
searchString,
});
};

search = (searchString: ?string) => {
this.setState({
searchString,
});
};

selectUser = (userId: string) => {
this.setState(prev => ({
selectedUsers: prev.selectedUsers.concat([userId]),
wipSearchString: '',
searchString: '',
}));
this.searchInput && this.searchInput.focus();
};

removeSelectedUser = (userId: string) => () => {
this.setState(prev => ({
selectedUsers: prev.selectedUsers.filter(
selectedId => selectedId !== userId
),
}));
};

onSubmit = (text: string) => {
if (this.state.selectedUsers.length === 0) return;
this.props
.createDirectMessageThread({
participants: this.state.selectedUsers,
message: {
messageType: 'text',
threadType: 'directMessageThread',
content: {
body: text,
},
},
})
.then((result: CreateDirectMessageThreadType) => {
const { navigation } = this.props;

return navigation.navigate('DirectMessageThread', {
id: result.data.createDirectMessageThread.id,
});
})
.catch(err => {
console.error(err);
});
};

filterResults = (results: Array<Object>) => {
const { currentUser } = this.props;
const { selectedUsers } = this.state;

return results
.filter(row => row.id !== currentUser.id)
.filter(row => selectedUsers.indexOf(row.id) < 0);
};

render() {
return (
<ComposerWrapper>
<SearchInputArea>
<SelectedUsers
horizontal
empty={this.state.selectedUsers.length === 0}
>
{this.state.selectedUsers.map(userId => (
<SelectedUser
key={userId}
id={userId}
keyboardShouldPersistTaps={'always'}
onPressHandler={this.removeSelectedUser(userId)}
/>
))}
</SelectedUsers>
<UserSearchInput
onChangeText={this.onChangeText}
onEndEditing={this.onFinishTyping}
onSubmitEditing={this.onFinishTyping}
value={this.state.wipSearchString}
returnKeyType="search"
autoFocus
autoCorrect={false}
autoCapitalize={'none'}
innerRef={elem => (this.searchInput = elem)}
placeholder={'Search for people...'}
/>
</SearchInputArea>

{!this.state.searchString && (
<FullscreenNullState title={''} subtitle={''} />
)}

{this.state.searchString && (
<PeopleSearchView
onPress={this.selectUser}
queryString={this.state.searchString}
keyboardShouldPersistTaps={'always'}
filter={results => this.filterResults(results)}
/>
)}

<ChatInput
onSubmit={this.onSubmit}
disableSubmit={this.state.selectedUsers.length === 0}
/>
</ComposerWrapper>
);
}
}

export default compose(
withCurrentUser,
getCurrentUserDMThreadConnection,
createDirectMessageThread,
withSafeView
)(DirectMessageComposer);
Loading

0 comments on commit 69052f0

Please sign in to comment.