Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 8961c87

Browse files
committed
feat: Autocomplete selection wraparound
1 parent cd928fe commit 8961c87

File tree

3 files changed

+59
-16
lines changed

3 files changed

+59
-16
lines changed

.eslintrc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@
3636
"no-new-wrappers": ["error"],
3737
"no-invalid-regexp": ["error"],
3838
"no-extra-bind": ["error"],
39-
"no-magic-numbers": ["error"],
39+
"no-magic-numbers": ["error", {
40+
"ignore": [-1, 0, 1], // usually used in array/string indexing
41+
"ignoreArrayIndexes": true,
42+
"enforceConst": true,
43+
"detectObjects": true
44+
}],
4045
"consistent-return": ["error"],
4146
"valid-jsdoc": ["error"],
4247
"no-use-before-define": ["error"],

src/RichText.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SelectionState
1010
} from 'draft-js';
1111
import * as sdk from './index';
12+
import * as emojione from 'emojione';
1213

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

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

3942
export function contentStateToHTML(contentState: ContentState): string {
4043
return contentState.getBlockMap().map((block) => {
@@ -89,6 +92,7 @@ export function getScopedRTDecorators(scope: any): CompositeDecorator {
8992
return <span className="mx_UserPill">{avatar} {props.children}</span>;
9093
}
9194
};
95+
9296
let roomDecorator = {
9397
strategy: (contentBlock, callback) => {
9498
findWithRegex(ROOM_REGEX, contentBlock, callback);
@@ -98,6 +102,16 @@ export function getScopedRTDecorators(scope: any): CompositeDecorator {
98102
}
99103
};
100104

105+
// Unused for now, due to https://github.com/facebook/draft-js/issues/414
106+
let emojiDecorator = {
107+
strategy: (contentBlock, callback) => {
108+
findWithRegex(EMOJI_REGEX, contentBlock, callback);
109+
},
110+
component: (props) => {
111+
return <span dangerouslySetInnerHTML={{__html: ' ' + emojione.unicodeToImage(props.children[0].props.text)}}/>
112+
}
113+
};
114+
101115
return [usernameDecorator, roomDecorator];
102116
}
103117

src/components/views/rooms/Autocomplete.js

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,28 @@ export default class Autocomplete extends React.Component {
1111
completions: [],
1212

1313
// how far down the completion list we are
14-
selectionOffset: 0
14+
selectionOffset: 0,
1515
};
1616
}
1717

1818
componentWillReceiveProps(props, state) {
19-
if(props.query == this.props.query) return;
19+
if (props.query === this.props.query) {
20+
return;
21+
}
2022

21-
getCompletions(props.query, props.selection).map(completionResult => {
23+
getCompletions(props.query, props.selection).forEach(completionResult => {
2224
try {
2325
completionResult.completions.then(completions => {
2426
let i = this.state.completions.findIndex(
2527
completion => completion.provider === completionResult.provider
2628
);
2729

28-
i = i == -1 ? this.state.completions.length : i;
30+
i = i === -1 ? this.state.completions.length : i;
2931
let newCompletions = Object.assign([], this.state.completions);
3032
completionResult.completions = completions;
3133
newCompletions[i] = completionResult;
3234
this.setState({
33-
completions: newCompletions
35+
completions: newCompletions,
3436
});
3537
}, err => {
3638
console.error(err);
@@ -42,13 +44,25 @@ export default class Autocomplete extends React.Component {
4244
});
4345
}
4446

45-
onUpArrow() {
46-
this.setState({selectionOffset: this.state.selectionOffset - 1});
47+
countCompletions(): number {
48+
return this.state.completions.map(completionResult => {
49+
return completionResult.completions.length;
50+
}).reduce((l, r) => l + r);
51+
}
52+
53+
// called from MessageComposerInput
54+
onUpArrow(): boolean {
55+
let completionCount = this.countCompletions(),
56+
selectionOffset = (completionCount + this.state.selectionOffset - 1) % completionCount;
57+
this.setState({selectionOffset});
4758
return true;
4859
}
4960

50-
onDownArrow() {
51-
this.setState({selectionOffset: this.state.selectionOffset + 1});
61+
// called from MessageComposerInput
62+
onDownArrow(): boolean {
63+
let completionCount = this.countCompletions(),
64+
selectionOffset = (this.state.selectionOffset + 1) % completionCount;
65+
this.setState({selectionOffset});
5266
return true;
5367
}
5468

@@ -58,18 +72,20 @@ export default class Autocomplete extends React.Component {
5872
let completions = completionResult.completions.map((completion, i) => {
5973
let Component = completion.component;
6074
let className = classNames('mx_Autocomplete_Completion', {
61-
'selected': position == this.state.selectionOffset
75+
'selected': position === this.state.selectionOffset,
6276
});
6377
let componentPosition = position;
6478
position++;
65-
if(Component) {
79+
if (Component) {
6680
return Component;
6781
}
82+
83+
let onMouseOver = () => this.setState({selectionOffset: componentPosition});
6884

6985
return (
7086
<div key={i}
7187
className={className}
72-
onMouseOver={() => this.setState({selectionOffset: componentPosition})}>
88+
onMouseOver={onMouseOver}>
7389
<span style={{fontWeight: 600}}>{completion.title}</span>
7490
<span>{completion.subtitle}</span>
7591
<span style={{flex: 1}} />
@@ -82,7 +98,11 @@ export default class Autocomplete extends React.Component {
8298
return completions.length > 0 ? (
8399
<div key={i} className="mx_Autocomplete_ProviderSection">
84100
<span className="mx_Autocomplete_provider_name">{completionResult.provider.getName()}</span>
85-
<ReactCSSTransitionGroup component="div" transitionName="autocomplete" transitionEnterTimeout={300} transitionLeaveTimeout={300}>
101+
<ReactCSSTransitionGroup
102+
component="div"
103+
transitionName="autocomplete"
104+
transitionEnterTimeout={300}
105+
transitionLeaveTimeout={300}>
86106
{completions}
87107
</ReactCSSTransitionGroup>
88108
</div>
@@ -91,7 +111,11 @@ export default class Autocomplete extends React.Component {
91111

92112
return (
93113
<div className="mx_Autocomplete">
94-
<ReactCSSTransitionGroup component="div" transitionName="autocomplete" transitionEnterTimeout={300} transitionLeaveTimeout={300}>
114+
<ReactCSSTransitionGroup
115+
component="div"
116+
transitionName="autocomplete"
117+
transitionEnterTimeout={300}
118+
transitionLeaveTimeout={300}>
95119
{renderedCompletions}
96120
</ReactCSSTransitionGroup>
97121
</div>
@@ -101,5 +125,5 @@ export default class Autocomplete extends React.Component {
101125

102126
Autocomplete.propTypes = {
103127
// the query string for which to show autocomplete suggestions
104-
query: React.PropTypes.string.isRequired
128+
query: React.PropTypes.string.isRequired,
105129
};

0 commit comments

Comments
 (0)