From b9d7743e5a30f91c71c6d57c5f0e2a0695a4852d Mon Sep 17 00:00:00 2001 From: Aviral Dasgupta Date: Fri, 17 Jun 2016 04:58:09 +0530 Subject: [PATCH] Emoji provider, DDG working, style improvements --- package.json | 2 + src/autocomplete/Autocompleter.js | 4 +- src/autocomplete/Components.js | 13 +++++ src/autocomplete/DuckDuckGoProvider.js | 47 +++++++++++++------ src/autocomplete/EmojiProvider.js | 41 ++++++++++++++++ src/components/views/rooms/Autocomplete.js | 43 +++++++++-------- src/components/views/rooms/MessageComposer.js | 5 +- .../views/rooms/MessageComposerInput.js | 9 +++- 8 files changed, 127 insertions(+), 37 deletions(-) create mode 100644 src/autocomplete/Components.js create mode 100644 src/autocomplete/EmojiProvider.js diff --git a/package.json b/package.json index 586f060b01..82ac307710 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "draft-js-export-html": "^0.2.2", "draft-js-export-markdown": "^0.2.0", "draft-js-import-markdown": "^0.1.6", + "emojione": "^2.2.2", "favico.js": "^0.3.10", "filesize": "^3.1.2", "flux": "^2.0.3", @@ -39,6 +40,7 @@ "optimist": "^0.6.1", "q": "^1.4.1", "react": "^15.0.1", + "react-addons-css-transition-group": "^15.1.0", "react-dom": "^15.0.1", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#c3d942e", "sanitize-html": "^1.11.1", diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js index a8ed2da59a..c8f3134a3b 100644 --- a/src/autocomplete/Autocompleter.js +++ b/src/autocomplete/Autocompleter.js @@ -2,12 +2,14 @@ import CommandProvider from './CommandProvider'; import DuckDuckGoProvider from './DuckDuckGoProvider'; import RoomProvider from './RoomProvider'; import UserProvider from './UserProvider'; +import EmojiProvider from './EmojiProvider'; const PROVIDERS = [ CommandProvider, DuckDuckGoProvider, RoomProvider, - UserProvider + UserProvider, + EmojiProvider ].map(completer => new completer()); export function getCompletions(query: String) { diff --git a/src/autocomplete/Components.js b/src/autocomplete/Components.js new file mode 100644 index 0000000000..cb7d56f9bf --- /dev/null +++ b/src/autocomplete/Components.js @@ -0,0 +1,13 @@ +export function TextualCompletion(props: { + title: ?string, + subtitle: ?string, + description: ?string +}) { + return ( +
+ {completion.title} + {completion.subtitle} + {completion.description} +
+ ); +} diff --git a/src/autocomplete/DuckDuckGoProvider.js b/src/autocomplete/DuckDuckGoProvider.js index 6545b96cbd..2acd892498 100644 --- a/src/autocomplete/DuckDuckGoProvider.js +++ b/src/autocomplete/DuckDuckGoProvider.js @@ -2,31 +2,50 @@ import AutocompleteProvider from './AutocompleteProvider'; import Q from 'q'; import 'whatwg-fetch'; -const DDG_REGEX = /\/ddg\w+(.+)$/; +const DDG_REGEX = /\/ddg\s+(.+)$/; const REFERER = 'vector'; export default class DuckDuckGoProvider extends AutocompleteProvider { static getQueryUri(query: String) { - return `http://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&t=${encodeURIComponent(REFERER)}`; + return `http://api.duckduckgo.com/?q=${encodeURIComponent(query)}` + + `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERER)}`; } getCompletions(query: String) { - if(!query) + let match = DDG_REGEX.exec(query); + if(!query || !match) return Q.when([]); - let promise = Q.defer(); - fetch(DuckDuckGoProvider.getQueryUri(query), { + return fetch(DuckDuckGoProvider.getQueryUri(match[1]), { method: 'GET' - }).then(response => { - let results = response.Results.map(result => { - return { - title: result.Text, - description: result.Result - }; + }) + .then(response => response.json()) + .then(json => { + let results = json.Results.map(result => { + return { + title: result.Text, + description: result.Result + }; + }); + if(json.Answer) { + results.unshift({ + title: json.Answer, + description: json.AnswerType + }); + } + if(json.RelatedTopics && json.RelatedTopics.length > 0) { + results.unshift({ + title: json.RelatedTopics[0].Text + }); + } + if(json.AbstractText) { + results.unshift({ + title: json.AbstractText + }); + } + // console.log(results); + return results; }); - promise.resolve(results); - }); - return promise; } getName() { diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js new file mode 100644 index 0000000000..fefd00a7fd --- /dev/null +++ b/src/autocomplete/EmojiProvider.js @@ -0,0 +1,41 @@ +import AutocompleteProvider from './AutocompleteProvider'; +import Q from 'q'; +import {emojioneList, shortnameToImage} from 'emojione'; +import Fuse from 'fuse.js'; + +const EMOJI_REGEX = /:\w*:?/g; +const EMOJI_SHORTNAMES = Object.keys(emojioneList); + +export default class EmojiProvider extends AutocompleteProvider { + constructor() { + super(); + console.log(EMOJI_SHORTNAMES); + this.fuse = new Fuse(EMOJI_SHORTNAMES); + } + + getCompletions(query: String) { + let completions = []; + const matches = query.match(EMOJI_REGEX); + console.log(matches); + if(!!matches) { + const command = matches[0]; + completions = this.fuse.search(command).map(result => { + let shortname = EMOJI_SHORTNAMES[result]; + let imageHTML = shortnameToImage(shortname); + return { + title: shortname, + component: ( +
+ {shortname} +
+ ) + }; + }).slice(0, 4); + } + return Q.when(completions); + } + + getName() { + return 'Emoji'; + } +} diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js index 4bc4102070..673cdc5bf5 100644 --- a/src/components/views/rooms/Autocomplete.js +++ b/src/components/views/rooms/Autocomplete.js @@ -1,4 +1,5 @@ import React from 'react'; +import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import {getCompletions} from '../../../autocomplete/Autocompleter'; @@ -11,8 +12,11 @@ export default class Autocomplete extends React.Component { } componentWillReceiveProps(props, state) { + if(props.query == this.props.query) return; + getCompletions(props.query).map(completionResult => { try { + console.log(`${completionResult.provider.getName()}: ${JSON.stringify(completionResult.completions)}`); completionResult.completions.then(completions => { let i = this.state.completions.findIndex( completion => completion.provider === completionResult.provider @@ -23,15 +27,16 @@ export default class Autocomplete extends React.Component { let newCompletions = Object.assign([], this.state.completions); completionResult.completions = completions; newCompletions[i] = completionResult; - console.log(newCompletions); + // console.log(newCompletions); this.setState({ completions: newCompletions }); }, err => { - + console.error(err); }); } catch (e) { // An error in one provider shouldn't mess up the rest. + console.error(e); } }); } @@ -42,23 +47,19 @@ export default class Autocomplete extends React.Component { const position = pinElement.getBoundingClientRect(); - const style = { - position: 'fixed', - border: '1px solid gray', - background: 'white', - borderRadius: '4px' - }; - this.props.pinTo.forEach(direction => { - style[direction] = position[direction]; - }); const renderedCompletions = this.state.completions.map((completionResult, i) => { - console.log(completionResult); + // console.log(completionResult); let completions = completionResult.completions.map((completion, i) => { + let Component = completion.component; + if(Component) { + return Component; + } + return ( -
- {completion.title} +
+ {completion.title} {completion.subtitle} {completion.description}
@@ -67,16 +68,20 @@ export default class Autocomplete extends React.Component { return completions.length > 0 ? ( -
- {completionResult.provider.getName()} - {completions} +
+ {completionResult.provider.getName()} + + {completions} +
) : null; }); return ( -
- {renderedCompletions} +
+ + {renderedCompletions} +
); } diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 889415f243..0f9dd86b09 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -212,13 +212,14 @@ module.exports = React.createClass({ return (
+
+ +
{controls}
- -
); } diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 8a0ee7d8a8..a9ee764864 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -352,6 +352,10 @@ export default class MessageComposerInput extends React.Component { } else { this.onFinishedTyping(); } + + if(this.props.onContentChanged) { + this.props.onContentChanged(editorState.getCurrentContent().getPlainText()); + } } enableRichtext(enabled: boolean) { @@ -521,5 +525,8 @@ MessageComposerInput.propTypes = { onResize: React.PropTypes.func, // js-sdk Room object - room: React.PropTypes.object.isRequired + room: React.PropTypes.object.isRequired, + + // called with current plaintext content (as a string) whenever it changes + onContentChanged: React.PropTypes.func };