Skip to content

Commit

Permalink
feat: Autocomplete selection wraparound
Browse files Browse the repository at this point in the history
  • Loading branch information
aviraldg committed Jul 2, 2016
1 parent cd928fe commit 8961c87
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 16 deletions.
7 changes: 6 additions & 1 deletion .eslintrc
Expand Up @@ -36,7 +36,12 @@
"no-new-wrappers": ["error"], "no-new-wrappers": ["error"],
"no-invalid-regexp": ["error"], "no-invalid-regexp": ["error"],
"no-extra-bind": ["error"], "no-extra-bind": ["error"],
"no-magic-numbers": ["error"], "no-magic-numbers": ["error", {
"ignore": [-1, 0, 1], // usually used in array/string indexing
"ignoreArrayIndexes": true,
"enforceConst": true,
"detectObjects": true
}],
"consistent-return": ["error"], "consistent-return": ["error"],
"valid-jsdoc": ["error"], "valid-jsdoc": ["error"],
"no-use-before-define": ["error"], "no-use-before-define": ["error"],
Expand Down
14 changes: 14 additions & 0 deletions src/RichText.js
Expand Up @@ -9,6 +9,7 @@ import {
SelectionState SelectionState
} from 'draft-js'; } from 'draft-js';
import * as sdk from './index'; import * as sdk from './index';
import * as emojione from 'emojione';


const BLOCK_RENDER_MAP = DefaultDraftBlockRenderMap.set('unstyled', { const BLOCK_RENDER_MAP = DefaultDraftBlockRenderMap.set('unstyled', {
element: 'span' element: 'span'
Expand All @@ -35,6 +36,8 @@ const MARKDOWN_REGEX = {


const USERNAME_REGEX = /@\S+:\S+/g; const USERNAME_REGEX = /@\S+:\S+/g;
const ROOM_REGEX = /#\S+:\S+/g; const ROOM_REGEX = /#\S+:\S+/g;
let EMOJI_REGEX = null;
window.EMOJI_REGEX = EMOJI_REGEX = new RegExp(emojione.unicodeRegexp, 'g');


export function contentStateToHTML(contentState: ContentState): string { export function contentStateToHTML(contentState: ContentState): string {
return contentState.getBlockMap().map((block) => { return contentState.getBlockMap().map((block) => {
Expand Down Expand Up @@ -89,6 +92,7 @@ export function getScopedRTDecorators(scope: any): CompositeDecorator {
return <span className="mx_UserPill">{avatar} {props.children}</span>; return <span className="mx_UserPill">{avatar} {props.children}</span>;
} }
}; };

let roomDecorator = { let roomDecorator = {
strategy: (contentBlock, callback) => { strategy: (contentBlock, callback) => {
findWithRegex(ROOM_REGEX, contentBlock, callback); findWithRegex(ROOM_REGEX, contentBlock, callback);
Expand All @@ -98,6 +102,16 @@ export function getScopedRTDecorators(scope: any): CompositeDecorator {
} }
}; };


// Unused for now, due to https://github.com/facebook/draft-js/issues/414
let emojiDecorator = {
strategy: (contentBlock, callback) => {
findWithRegex(EMOJI_REGEX, contentBlock, callback);
},
component: (props) => {
return <span dangerouslySetInnerHTML={{__html: ' ' + emojione.unicodeToImage(props.children[0].props.text)}}/>
}
};

return [usernameDecorator, roomDecorator]; return [usernameDecorator, roomDecorator];
} }


Expand Down
54 changes: 39 additions & 15 deletions src/components/views/rooms/Autocomplete.js
Expand Up @@ -11,26 +11,28 @@ export default class Autocomplete extends React.Component {
completions: [], completions: [],


// how far down the completion list we are // how far down the completion list we are
selectionOffset: 0 selectionOffset: 0,
}; };
} }


componentWillReceiveProps(props, state) { componentWillReceiveProps(props, state) {
if(props.query == this.props.query) return; if (props.query === this.props.query) {
return;
}


getCompletions(props.query, props.selection).map(completionResult => { getCompletions(props.query, props.selection).forEach(completionResult => {
try { try {
completionResult.completions.then(completions => { completionResult.completions.then(completions => {
let i = this.state.completions.findIndex( let i = this.state.completions.findIndex(
completion => completion.provider === completionResult.provider completion => completion.provider === completionResult.provider
); );


i = i == -1 ? this.state.completions.length : i; i = i === -1 ? this.state.completions.length : i;
let newCompletions = Object.assign([], this.state.completions); let newCompletions = Object.assign([], this.state.completions);
completionResult.completions = completions; completionResult.completions = completions;
newCompletions[i] = completionResult; newCompletions[i] = completionResult;
this.setState({ this.setState({
completions: newCompletions completions: newCompletions,
}); });
}, err => { }, err => {
console.error(err); console.error(err);
Expand All @@ -42,13 +44,25 @@ export default class Autocomplete extends React.Component {
}); });
} }


onUpArrow() { countCompletions(): number {
this.setState({selectionOffset: this.state.selectionOffset - 1}); return this.state.completions.map(completionResult => {
return completionResult.completions.length;
}).reduce((l, r) => l + r);
}

// called from MessageComposerInput
onUpArrow(): boolean {
let completionCount = this.countCompletions(),
selectionOffset = (completionCount + this.state.selectionOffset - 1) % completionCount;
this.setState({selectionOffset});
return true; return true;
} }


onDownArrow() { // called from MessageComposerInput
this.setState({selectionOffset: this.state.selectionOffset + 1}); onDownArrow(): boolean {
let completionCount = this.countCompletions(),
selectionOffset = (this.state.selectionOffset + 1) % completionCount;
this.setState({selectionOffset});
return true; return true;
} }


Expand All @@ -58,18 +72,20 @@ export default class Autocomplete extends React.Component {
let completions = completionResult.completions.map((completion, i) => { let completions = completionResult.completions.map((completion, i) => {
let Component = completion.component; let Component = completion.component;
let className = classNames('mx_Autocomplete_Completion', { let className = classNames('mx_Autocomplete_Completion', {
'selected': position == this.state.selectionOffset 'selected': position === this.state.selectionOffset,
}); });
let componentPosition = position; let componentPosition = position;
position++; position++;
if(Component) { if (Component) {
return Component; return Component;
} }

let onMouseOver = () => this.setState({selectionOffset: componentPosition});


return ( return (
<div key={i} <div key={i}
className={className} className={className}
onMouseOver={() => this.setState({selectionOffset: componentPosition})}> onMouseOver={onMouseOver}>
<span style={{fontWeight: 600}}>{completion.title}</span> <span style={{fontWeight: 600}}>{completion.title}</span>
<span>{completion.subtitle}</span> <span>{completion.subtitle}</span>
<span style={{flex: 1}} /> <span style={{flex: 1}} />
Expand All @@ -82,7 +98,11 @@ export default class Autocomplete extends React.Component {
return completions.length > 0 ? ( return completions.length > 0 ? (
<div key={i} className="mx_Autocomplete_ProviderSection"> <div key={i} className="mx_Autocomplete_ProviderSection">
<span className="mx_Autocomplete_provider_name">{completionResult.provider.getName()}</span> <span className="mx_Autocomplete_provider_name">{completionResult.provider.getName()}</span>
<ReactCSSTransitionGroup component="div" transitionName="autocomplete" transitionEnterTimeout={300} transitionLeaveTimeout={300}> <ReactCSSTransitionGroup
component="div"
transitionName="autocomplete"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}>
{completions} {completions}
</ReactCSSTransitionGroup> </ReactCSSTransitionGroup>
</div> </div>
Expand All @@ -91,7 +111,11 @@ export default class Autocomplete extends React.Component {


return ( return (
<div className="mx_Autocomplete"> <div className="mx_Autocomplete">
<ReactCSSTransitionGroup component="div" transitionName="autocomplete" transitionEnterTimeout={300} transitionLeaveTimeout={300}> <ReactCSSTransitionGroup
component="div"
transitionName="autocomplete"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}>
{renderedCompletions} {renderedCompletions}
</ReactCSSTransitionGroup> </ReactCSSTransitionGroup>
</div> </div>
Expand All @@ -101,5 +125,5 @@ export default class Autocomplete extends React.Component {


Autocomplete.propTypes = { Autocomplete.propTypes = {
// the query string for which to show autocomplete suggestions // the query string for which to show autocomplete suggestions
query: React.PropTypes.string.isRequired query: React.PropTypes.string.isRequired,
}; };

0 comments on commit 8961c87

Please sign in to comment.