Skip to content

Commit

Permalink
refactor: improve code quality
Browse files Browse the repository at this point in the history
This is based on code climate suggestions
  • Loading branch information
ifiokjr committed Jul 10, 2019
1 parent 3e21459 commit a38ee56
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 121 deletions.
93 changes: 56 additions & 37 deletions @remirror/editor-twitter/src/components/suggestions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MentionExtensionAttrs } from '@remirror/extension-mention';
import { Attrs, EditorView } from '@remirror/core';
import { MentionExtensionAttrs, SuggestionStateMatch } from '@remirror/extension-mention';
import { useRemirror } from '@remirror/react';
import React, { FunctionComponent } from 'react';
import { styled } from '../twitter-theme';
Expand Down Expand Up @@ -51,6 +52,45 @@ const AtUsername = styled.span`
color: #657786;
`;

interface CreateOnClickMethodFactoryParams {
getMention: () => SuggestionStateMatch;
setExitTriggeredInternally: () => void;
view: EditorView;
command(attrs: Attrs): void;
}

/**
* This method helps create the onclick factory method used by both types of suggestions supported
*/
const createOnClickMethodFactory = ({
getMention,
setExitTriggeredInternally,
view,
command,
}: CreateOnClickMethodFactoryParams) => (id: string) => () => {
const { char, name, range } = getMention();

const params: MentionExtensionAttrs = {
id,
label: `${char}${id}`,
name,
replacementType: 'full',
range,
role: 'presentation',
href: `/${id}`,
};

setExitTriggeredInternally(); // Prevents further `onExit` calls
command(params);

if (!view.hasFocus()) {
view.focus();
}
};

/**
* Render the suggestions for mentioning a user.
*/
export const AtSuggestions: FunctionComponent<UserSuggestionsProps> = ({
getMention,
data,
Expand All @@ -61,24 +101,12 @@ export const AtSuggestions: FunctionComponent<UserSuggestionsProps> = ({
/**
* Click handler for accepting a user suggestion
*/
const onClickFactory = (username: string) => () => {
const params: MentionExtensionAttrs = {
id: username,
label: `@${username}`,
name: 'at',
replacementType: 'full',
range: getMention().range,
role: 'presentation',
href: `/${username}`,
};

setExitTriggeredInternally(); // Prevents further `onExit` calls
actions.mentionUpdate.command(params);

if (!view.hasFocus()) {
view.focus();
}
};
const onClickFactory = createOnClickMethodFactory({
command: actions.mentionUpdate.command,
getMention,
setExitTriggeredInternally,
view,
});

return (
<SuggestionsDropdown role='presentation'>
Expand Down Expand Up @@ -113,6 +141,9 @@ const HashTagText = styled.span`
}
`;

/**
* Render the suggestions for tagging.
*/
export const TagSuggestions: FunctionComponent<TagSuggestionsProps> = ({
getMention,
data,
Expand All @@ -123,24 +154,12 @@ export const TagSuggestions: FunctionComponent<TagSuggestionsProps> = ({
/**
* Click handler for accepting a tag suggestion
*/
const onClickFactory = (tag: string) => () => {
const params: MentionExtensionAttrs = {
id: tag,
label: `#${tag}`,
name: 'tag',
replacementType: 'full',
range: getMention().range,
role: 'presentation',
href: `/search?query=${tag}`,
};

setExitTriggeredInternally(); // Prevents futher `onExit` calls
actions.mentionUpdate.command(params);

if (!view.hasFocus()) {
view.focus();
}
};
const onClickFactory = createOnClickMethodFactory({
command: actions.mentionUpdate.command,
getMention,
setExitTriggeredInternally,
view,
});

return (
<SuggestionsDropdown role='presentation'>
Expand Down
149 changes: 65 additions & 84 deletions @remirror/editor-twitter/src/components/twitter.editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
OptionalSuggestionMatcher,
SuggestionCallback,
SuggestionKeyBindingMap,
SuggestionKeyBindingParams,
SuggestionStateMatch,
} from '@remirror/extension-mention';
import { ManagedRemirrorProvider, RemirrorExtension, RemirrorManager } from '@remirror/react';
Expand All @@ -19,9 +20,11 @@ import {
ActiveTagData,
ActiveUserData,
MatchName,
MentionState,
OnMentionChangeParams,
TwitterEditorProps,
} from '../twitter-types';
import { calculateNewIndexFromArrowPress, mapToActiveIndex } from '../twitter-utils';
import { TwitterEditorComponent } from './editor';
import { EmojiPickerProps } from './emoji-picker';

Expand Down Expand Up @@ -67,94 +70,76 @@ export class TwitterEditor extends PureComponent<TwitterEditorProps, State> {
*/
private exitTriggeredInternally = false;

/**
* Create the arrow bindings.
*/
private createArrowBindings = (direction: 'up' | 'down') => ({
query,
name,
}: SuggestionKeyBindingParams) => {
const { activeIndex: prevIndex, hideSuggestions } = this.state;
const { onMentionChange: onMentionStateChange } = this.props;

const matches = name === 'at' ? this.users : this.tags;

if (hideSuggestions || !matches.length) {
return false;
}

// pressed up arrow
const activeIndex = calculateNewIndexFromArrowPress({
direction,
matchLength: matches.length,
prevIndex,
});

this.setState({ activeIndex, activeMatcher: name as MatchName });
onMentionStateChange({ name, query: query.full, activeIndex } as OnMentionChangeParams);

return true;
};

/**
* These are the keyBindings for mentions extension. This allows for overriding
*/
private keyBindings: SuggestionKeyBindingMap = {
/**
* Handle the enter key being pressed
*/
Enter: ({ name, command }) => {
Enter: ({ name, command, char }) => {
const { activeIndex, hideSuggestions } = this.state;

if (hideSuggestions) {
return false;
}

if (name === 'at' && this.users.length) {
const { username } = this.users[activeIndex];
this.setExitTriggeredInternally();
command({
replacementType: 'full',
id: username,
label: `@${username}`,
role: 'presentation',
href: `/${username}`,
});

return true;
}

if (name === 'tag' && this.tags.length) {
const { tag } = this.tags[activeIndex];

this.setExitTriggeredInternally();
command({
replacementType: 'full',
id: tag,
label: `#${tag}`,
role: 'presentation',
href: `/search?query=${tag}`,
});
const id =
name === 'at' && this.users.length
? this.users[activeIndex].username
: name === 'tag' && this.tags.length
? this.tags[activeIndex].tag
: undefined;

return true;
}

return false;
},

/**
* Handle the up arrow being pressed
*/
ArrowUp: ({ query, name }) => {
const { activeIndex: prevIndex, hideSuggestions } = this.state;
const { onMentionChange: onMentionStateChange } = this.props;

const matches = name === 'at' ? this.users : this.tags;

if (hideSuggestions || !matches.length) {
// Check if a matching id exists because the user has selected something.
if (!id) {
return false;
}

// pressed up arrow
const activeIndex = prevIndex - 1 < 0 ? matches.length - 1 : prevIndex - 1;

this.setState({ activeIndex, activeMatcher: name as MatchName });
onMentionStateChange({ name, query: query.full, activeIndex } as OnMentionChangeParams);
this.setExitTriggeredInternally();
command({
replacementType: 'full',
id,
label: `${char}${id}`,
role: 'presentation',
href: `/${id}`,
});

return true;
},

/**
* Handle the down arrow being pressed
* Hide the suggestions when the escape key is pressed.
*/
ArrowDown: ({ query, name }) => {
const { activeIndex: prevIndex, hideSuggestions } = this.state;
const { onMentionChange: onMentionStateChange } = this.props;
const matches = name === 'at' ? this.users : this.tags;

if (hideSuggestions || !matches.length) {
return false;
}

const activeIndex = prevIndex + 1 > matches.length - 1 ? 0 : prevIndex + 1;

this.setState({ activeIndex, activeMatcher: name as MatchName });
onMentionStateChange({ name, query: query.full, activeIndex } as OnMentionChangeParams);

return true;
},

Escape: ({ name }) => {
const matches = name === 'at' ? this.users : this.tags;

Expand All @@ -165,26 +150,30 @@ export class TwitterEditor extends PureComponent<TwitterEditorProps, State> {
this.setState({ hideSuggestions: true });
return true;
},

/**
* Handle the up arrow being pressed
*/
ArrowUp: this.createArrowBindings('up'),

/**
* Handle the down arrow being pressed
*/
ArrowDown: this.createArrowBindings('down'),
};

/**
* The list of users that match the current query
*/
private get users(): ActiveUserData[] {
return this.props.userData.map((user, index) => ({
...user,
active: index === this.state.activeIndex,
}));
return mapToActiveIndex(this.props.userData, this.state.activeIndex);
}

/**
* The list of tags which match the current query
*/
private get tags(): ActiveTagData[] {
return this.props.tagData.map((data, index) => ({
...data,
active: index === this.state.activeIndex,
}));
return mapToActiveIndex(this.props.tagData, this.state.activeIndex);
}

/**
Expand Down Expand Up @@ -221,19 +210,11 @@ export class TwitterEditor extends PureComponent<TwitterEditorProps, State> {
private onChange: SuggestionCallback = params => {
const { query, name } = params;

if (name === 'at') {
const props = {
name: 'at' as 'at',
query: query.full,
};
this.props.onMentionChange({ ...props, activeIndex: this.state.activeIndex });
}

if (name === 'tag') {
if (name) {
const props = {
name: 'tag' as 'tag',
name,
query: query.full,
};
} as MentionState;
this.props.onMentionChange({ ...props, activeIndex: this.state.activeIndex });
}

Expand Down
41 changes: 41 additions & 0 deletions @remirror/editor-twitter/src/twitter-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Maps the items to items with an active property
*/
export const mapToActiveIndex = <GItem extends {}>(
items: GItem[],
activeIndex: number,
): Array<GItem & { active: boolean }> => {
return items.map((item, index) => ({
...item,
active: index === activeIndex,
}));
};

interface CalculateNewIndexFromArrowPressParams {
/**
* Whether the arrow key was the up key or the down key
*/
direction: 'up' | 'down';

/**
* The total number of matches
*/
matchLength: number;

/**
* The previously matched index
*/
prevIndex: number;
}
export const calculateNewIndexFromArrowPress = ({
direction,
matchLength,
prevIndex,
}: CalculateNewIndexFromArrowPressParams) =>
direction === 'down'
? prevIndex + 1 > matchLength - 1
? 0
: prevIndex + 1
: prevIndex - 1 < 0
? matchLength - 1
: prevIndex - 1;

0 comments on commit a38ee56

Please sign in to comment.