Skip to content

Commit

Permalink
feat(mentions): Server-side filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
Mingze Xiao committed Jun 17, 2020
1 parent 7223fd4 commit 2226b21
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 37 deletions.
1 change: 0 additions & 1 deletion src/common/BaseAnnotator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ export default class BaseAnnotator extends EventEmitter {
protected hydrate(): void {
// Redux dispatch method signature doesn't seem to like async actions
this.store.dispatch<any>(store.fetchAnnotationsAction()); // eslint-disable-line @typescript-eslint/no-explicit-any
this.store.dispatch<any>(store.fetchCollaboratorsAction()); // eslint-disable-line @typescript-eslint/no-explicit-any
}

protected handleInitialized(): void {
Expand Down
54 changes: 21 additions & 33 deletions src/components/ReplyField/ReplyField.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as React from 'react';
import classnames from 'classnames';
import debounce from 'lodash/debounce';
import {
addMention,
getActiveMentionForEditorState,
} from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector/utils';
import fuzzySearch from 'box-ui-elements/es/utils/fuzzySearch';
import { DraftHandleValue, Editor, EditorState } from 'draft-js';
import PopupList from '../Popups/PopupList';
import { Collaborator } from '../../@types';
import { DEFAULT_COLLAB_DEBOUNCE } from '../../constants';
import { VirtualElement } from '../Popups/Popper';
import './ReplyField.scss';

Expand All @@ -28,6 +29,7 @@ export type Props = {
onChange: (editorState: EditorState) => void;
placeholder?: string;
setCursorPosition: (cursorPosition: number) => void;
updateCollaborators: (searchStr: string) => void;
};

export type State = {
Expand All @@ -42,6 +44,18 @@ export default class ReplyField extends React.Component<Props, State> {

state: State = { activeItemIndex: 0, popupReference: null };

updateCollaborators = debounce((editorState: EditorState): void => {
const { updateCollaborators } = this.props;

const activeMention = getActiveMentionForEditorState(editorState);
const trimmedQuery = activeMention?.mentionString.trim();
if (!trimmedQuery) {
return;
}

updateCollaborators(trimmedQuery);
}, DEFAULT_COLLAB_DEBOUNCE);

componentDidUpdate({ editorState: prevEditorState }: Props): void {
const { editorState } = this.props;

Expand All @@ -54,33 +68,6 @@ export default class ReplyField extends React.Component<Props, State> {
this.saveCursorPosition();
}

getCollaborators = (): Collaborator[] => {
const { collaborators, editorState } = this.props;

const activeMention = getActiveMentionForEditorState(editorState);
if (!activeMention) {
return [];
}

const trimmedQuery = activeMention.mentionString.trim();
// fuzzySearch doesn't match anything if query length is less than 2
// Compared to empty list, full list has a better user experience
if (trimmedQuery.length < 2) {
return collaborators;
}

return collaborators.filter(({ item }) => {
if (!item) {
return false;
}

const isNameMatch = fuzzySearch(trimmedQuery, item.name, 0);
const isEmailMatch = 'email' in item && fuzzySearch(trimmedQuery, item.email, 0);

return isNameMatch || isEmailMatch;
});
};

getVirtualElement = (activeMention: Mention): VirtualElement | null => {
const selection = window.getSelection();
if (!selection?.focusNode) {
Expand Down Expand Up @@ -133,14 +120,14 @@ export default class ReplyField extends React.Component<Props, State> {
handleChange = (nextEditorState: EditorState): void => {
const { onChange } = this.props;

this.updateCollaborators(nextEditorState);
onChange(nextEditorState);
};

handleSelect = (index: number): void => {
const { editorState } = this.props;
const { collaborators, editorState } = this.props;

const activeMention = getActiveMentionForEditorState(editorState);
const collaborators = this.getCollaborators();
const editorStateWithLink = addMention(editorState, activeMention, collaborators[index]);

this.handleChange(editorStateWithLink);
Expand All @@ -161,8 +148,9 @@ export default class ReplyField extends React.Component<Props, State> {
};

handleArrow = (event: React.KeyboardEvent): number => {
const { collaborators } = this.props;
const { popupReference } = this.state;
const { length } = this.getCollaborators();
const { length } = collaborators;

if (!popupReference || !length) {
return 0;
Expand Down Expand Up @@ -196,7 +184,7 @@ export default class ReplyField extends React.Component<Props, State> {
};

render(): JSX.Element {
const { className, editorState, isDisabled, placeholder, ...rest } = this.props;
const { className, collaborators, editorState, isDisabled, placeholder, ...rest } = this.props;
const { activeItemIndex, popupReference } = this.state;

return (
Expand All @@ -217,7 +205,7 @@ export default class ReplyField extends React.Component<Props, State> {
{popupReference && (
<PopupList
activeItemIndex={activeItemIndex}
items={this.getCollaborators()}
items={collaborators}
onActivate={this.setPopupListActiveItem}
onSelect={this.handleSelect}
reference={popupReference}
Expand Down
3 changes: 2 additions & 1 deletion src/components/ReplyField/ReplyFieldContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { AppState, getCollaborators, getCreatorCursor, setCursorAction } from '../../store';
import { AppState, fetchCollaboratorsAction, getCollaborators, getCreatorCursor, setCursorAction } from '../../store';
import ReplyField from './ReplyField';
import { Collaborator } from '../../@types';

Expand All @@ -15,6 +15,7 @@ export const mapStateToProps = (state: AppState): Props => ({

export const mapDispatchToProps = {
setCursorPosition: setCursorAction,
updateCollaborators: fetchCollaboratorsAction,
};

export default connect(mapStateToProps, mapDispatchToProps)(ReplyField);
2 changes: 2 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export const PLACEHOLDER_USER = {
id: '0',
email: '',
};

export const DEFAULT_COLLAB_DEBOUNCE = 500;
5 changes: 3 additions & 2 deletions src/store/users/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { AppThunkAPI } from '../types';
import { Collaborator } from '../../@types';
import { getFileId } from '../options';

export const fetchCollaboratorsAction = createAsyncThunk<APICollection<Collaborator>, undefined, AppThunkAPI>(
export const fetchCollaboratorsAction = createAsyncThunk<APICollection<Collaborator>, string, AppThunkAPI>(
'FETCH_COLLABORATORS',
async (arg, { extra, getState, signal }) => {
async (searchStr, { extra, getState, signal }) => {
// Create a new client for each request
const client = extra.api.getCollaboratorsAPI();
const state = getState();
Expand All @@ -20,6 +20,7 @@ export const fetchCollaboratorsAction = createAsyncThunk<APICollection<Collabora
// Wrap the client request in a promise to allow it to be returned and cancelled
return new Promise<APICollection<Collaborator>>((resolve, reject) => {
client.getFileCollaborators(fileId, resolve, reject, {
filter_term: searchStr, // eslint-disable-line @typescript-eslint/camelcase
include_groups: false, // eslint-disable-line @typescript-eslint/camelcase
include_uploader_collabs: false, // eslint-disable-line @typescript-eslint/camelcase
});
Expand Down

0 comments on commit 2226b21

Please sign in to comment.