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 (
);
}
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
};