rte improvements, markdown mode
This commit is contained in:
parent
bf8e56e04c
commit
e4217c3fb7
3 changed files with 168 additions and 151 deletions
|
@ -25,6 +25,8 @@
|
||||||
"classnames": "^2.1.2",
|
"classnames": "^2.1.2",
|
||||||
"draft-js": "^0.7.0",
|
"draft-js": "^0.7.0",
|
||||||
"draft-js-export-html": "^0.2.2",
|
"draft-js-export-html": "^0.2.2",
|
||||||
|
"draft-js-export-markdown": "^0.2.0",
|
||||||
|
"draft-js-import-markdown": "^0.1.6",
|
||||||
"favico.js": "^0.3.10",
|
"favico.js": "^0.3.10",
|
||||||
"filesize": "^3.1.2",
|
"filesize": "^3.1.2",
|
||||||
"flux": "^2.0.3",
|
"flux": "^2.0.3",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import {Editor, ContentState, convertFromHTML, DefaultDraftBlockRenderMap, DefaultDraftInlineStyle, CompositeDecorator} from 'draft-js';
|
import {Editor, ContentState, convertFromHTML, DefaultDraftBlockRenderMap, DefaultDraftInlineStyle, CompositeDecorator} from 'draft-js';
|
||||||
const ReactDOM = require('react-dom');
|
import * as sdk from './index';
|
||||||
var sdk = require('./index');
|
|
||||||
|
|
||||||
const BLOCK_RENDER_MAP = DefaultDraftBlockRenderMap.set('unstyled', {
|
const BLOCK_RENDER_MAP = DefaultDraftBlockRenderMap.set('unstyled', {
|
||||||
element: 'p' // draft uses <div> by default which we don't really like
|
element: 'p' // draft uses <div> by default which we don't really like, so we're using <p>
|
||||||
});
|
});
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
|
@ -14,21 +13,19 @@ const styles = {
|
||||||
UNDERLINE: 'u'
|
UNDERLINE: 'u'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function contentStateToHTML(contentState:ContentState): String {
|
export function contentStateToHTML(contentState: ContentState): string {
|
||||||
const elem = contentState.getBlockMap().map((block) => {
|
return contentState.getBlockMap().map((block) => {
|
||||||
const elem = BLOCK_RENDER_MAP.get(block.getType()).element;
|
let elem = BLOCK_RENDER_MAP.get(block.getType()).element;
|
||||||
const content = [];
|
let content = [];
|
||||||
block.findStyleRanges(() => true, (s, e) => {
|
block.findStyleRanges(() => true, (start, end) => {
|
||||||
const tags = block.getInlineStyleAt(s).map(style => styles[style]);
|
const tags = block.getInlineStyleAt(start).map(style => styles[style]);
|
||||||
const open = tags.map(tag => `<${tag}>`).join('');
|
const open = tags.map(tag => `<${tag}>`).join('');
|
||||||
const close = tags.map(tag => `</${tag}>`).reverse().join('');
|
const close = tags.map(tag => `</${tag}>`).reverse().join('');
|
||||||
content.push(`${open}${block.getText().substring(s, e)}${close}`);
|
content.push(`${open}${block.getText().substring(start, end)}${close}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (`<${elem}>${content.join('')}</${elem}>`);
|
return (`<${elem}>${content.join('')}</${elem}>`);
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
return elem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HTMLtoContentState(html:String): ContentState {
|
export function HTMLtoContentState(html:String): ContentState {
|
||||||
|
@ -38,6 +35,12 @@ export function HTMLtoContentState(html:String): ContentState {
|
||||||
const USERNAME_REGEX = /@\S+:\S+/g;
|
const USERNAME_REGEX = /@\S+:\S+/g;
|
||||||
const ROOM_REGEX = /#\S+:\S+/g;
|
const ROOM_REGEX = /#\S+:\S+/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a composite decorator which has access to provided scope.
|
||||||
|
*
|
||||||
|
* @param scope
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
export function getScopedDecorator(scope) {
|
export function getScopedDecorator(scope) {
|
||||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||||
|
|
||||||
|
@ -46,17 +49,13 @@ export function getScopedDecorator(scope) {
|
||||||
findWithRegex(USERNAME_REGEX, contentBlock, callback);
|
findWithRegex(USERNAME_REGEX, contentBlock, callback);
|
||||||
},
|
},
|
||||||
component: (props) => {
|
component: (props) => {
|
||||||
console.log(props.children);
|
let member = scope.room.getMember(props.children[0].props.text);
|
||||||
console.log(props.children[0].props.text);
|
|
||||||
const member = scope.room.getMember(props.children[0].props.text);
|
|
||||||
console.log(scope);
|
|
||||||
window.scope = scope;
|
|
||||||
let name = null;
|
let name = null;
|
||||||
if(!!member) {
|
if(!!member) {
|
||||||
name = member.name;
|
name = member.name;
|
||||||
}
|
}
|
||||||
console.log(member);
|
console.log(member);
|
||||||
const avatar = member ? <MemberAvatar member={member} width={16} height={16} /> : null;
|
let avatar = member ? <MemberAvatar member={member} width={16} height={16} /> : null;
|
||||||
return <span className="mx_UserPill">{avatar} {props.children}</span>;
|
return <span className="mx_UserPill">{avatar} {props.children}</span>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
var React = require("react");
|
import React from 'react';
|
||||||
|
|
||||||
var marked = require("marked");
|
var marked = require("marked");
|
||||||
marked.setOptions({
|
marked.setOptions({
|
||||||
|
@ -27,7 +27,11 @@ marked.setOptions({
|
||||||
smartypants: false
|
smartypants: false
|
||||||
});
|
});
|
||||||
|
|
||||||
import {Editor, EditorState, RichUtils, CompositeDecorator} from 'draft-js';
|
import {Editor, EditorState, RichUtils, CompositeDecorator,
|
||||||
|
convertFromRaw, convertToRaw, Modifier, EditorChangeType,
|
||||||
|
getDefaultKeyBinding, KeyBindingUtil, ContentState} from 'draft-js';
|
||||||
|
|
||||||
|
import {stateToMarkdown} from 'draft-js-export-markdown';
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var SlashCommands = require("../../../SlashCommands");
|
var SlashCommands = require("../../../SlashCommands");
|
||||||
|
@ -40,9 +44,20 @@ var KeyCode = require("../../../KeyCode");
|
||||||
|
|
||||||
import {contentStateToHTML, HTMLtoContentState, getScopedDecorator} from '../../../RichText';
|
import {contentStateToHTML, HTMLtoContentState, getScopedDecorator} from '../../../RichText';
|
||||||
|
|
||||||
var TYPING_USER_TIMEOUT = 10000;
|
const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
|
||||||
var TYPING_SERVER_TIMEOUT = 30000;
|
|
||||||
var MARKDOWN_ENABLED = true;
|
function mdownToHtml(mdown) {
|
||||||
|
var html = marked(mdown) || "";
|
||||||
|
html = html.trim();
|
||||||
|
// strip start and end <p> tags else you get 'orrible spacing
|
||||||
|
if (html.indexOf("<p>") === 0) {
|
||||||
|
html = html.substring("<p>".length);
|
||||||
|
}
|
||||||
|
if (html.lastIndexOf("</p>") === (html.length - "</p>".length)) {
|
||||||
|
html = html.substring(0, html.length - "</p>".length);
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The textInput part of the MessageComposer
|
* The textInput part of the MessageComposer
|
||||||
|
@ -54,13 +69,38 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.onInputClick = this.onInputClick.bind(this);
|
this.onInputClick = this.onInputClick.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
editorState: EditorState.createEmpty(getScopedDecorator(this.props))
|
isRichtextEnabled: true,
|
||||||
|
editorState: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// bit of a hack, but we need to do this here since createEditorState needs isRichtextEnabled
|
||||||
|
this.state.editorState = this.createEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getKeyBinding(e: SyntheticKeyboardEvent): string {
|
||||||
|
// C-m => Toggles between rich text and markdown modes
|
||||||
|
if(e.keyCode == 77 && KeyBindingUtil.isCtrlKeyCommand(e)) {
|
||||||
|
return 'toggle-mode';
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultKeyBinding(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Does the right thing" to create an EditorState, based on:
|
||||||
|
* - whether we've got rich text mode enabled
|
||||||
|
* - contentState was passed in
|
||||||
|
*/
|
||||||
|
createEditorState(contentState: ?ContentState): EditorState {
|
||||||
|
let func = contentState ? EditorState.createWithContent : EditorState.createEmpty;
|
||||||
|
let args = contentState ? [contentState] : [];
|
||||||
|
if(this.state.isRichtextEnabled) {
|
||||||
|
args.push(getScopedDecorator(this.props));
|
||||||
|
}
|
||||||
|
return func.apply(null, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.oldScrollHeight = 0;
|
|
||||||
this.markdownEnabled = MARKDOWN_ENABLED;
|
|
||||||
const component = this;
|
const component = this;
|
||||||
this.sentHistory = {
|
this.sentHistory = {
|
||||||
// The list of typed messages. Index 0 is more recent
|
// The list of typed messages. Index 0 is more recent
|
||||||
|
@ -132,7 +172,6 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.element.value = this.originalText;
|
this.element.value = this.originalText;
|
||||||
}
|
}
|
||||||
|
|
||||||
component.resizeInput();
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -140,18 +179,17 @@ export default class MessageComposerInput extends React.Component {
|
||||||
// save the currently entered text in order to restore it later.
|
// save the currently entered text in order to restore it later.
|
||||||
// NB: This isn't 'originalText' because we want to restore
|
// NB: This isn't 'originalText' because we want to restore
|
||||||
// sent history items too!
|
// sent history items too!
|
||||||
const contentHTML = contentStateToHTML(component.state.editorState.getCurrentContent());
|
let contentJSON = JSON.stringify(convertToRaw(component.state.editorState.getCurrentContent()));
|
||||||
console.error(contentHTML);
|
window.sessionStorage.setItem("input_" + this.roomId, contentJSON);
|
||||||
window.sessionStorage.setItem("input_" + this.roomId, contentHTML);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setLastTextEntry: function () {
|
setLastTextEntry: function () {
|
||||||
const contentHTML = window.sessionStorage.getItem("input_" + this.roomId);
|
let contentJSON = window.sessionStorage.getItem("input_" + this.roomId);
|
||||||
console.error(contentHTML);
|
if (contentJSON) {
|
||||||
if (contentHTML) {
|
let content = convertFromRaw(JSON.parse(contentJSON));
|
||||||
const content = HTMLtoContentState(contentHTML);
|
component.setState({
|
||||||
component.setState({editorState: EditorState.createWithContent(content, getScopedDecorator(component.props))});
|
editorState: component.createEditorState(content)
|
||||||
component.resizeInput();
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -163,10 +201,10 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.refs.editor,
|
this.refs.editor,
|
||||||
this.props.room.roomId
|
this.props.room.roomId
|
||||||
);
|
);
|
||||||
this.resizeInput();
|
// this is disabled for now, since https://github.com/matrix-org/matrix-react-sdk/pull/296 will land soon
|
||||||
if (this.props.tabComplete) {
|
// if (this.props.tabComplete) {
|
||||||
this.props.tabComplete.setTextArea(this.refs.editor);
|
// this.props.tabComplete.setEditor(this.refs.editor);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -176,44 +214,33 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
onAction(payload) {
|
onAction(payload) {
|
||||||
var editor = this.refs.editor;
|
var editor = this.refs.editor;
|
||||||
|
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'focus_composer':
|
case 'focus_composer':
|
||||||
editor.focus();
|
editor.focus();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// TODO change this so we insert a complete user alias
|
||||||
|
|
||||||
case 'insert_displayname':
|
case 'insert_displayname':
|
||||||
console.error('fixme');
|
if (this.state.editorState.getCurrentContent().hasText()) {
|
||||||
if (textarea.value.length) {
|
console.log(payload);
|
||||||
var left = textarea.value.substring(0, textarea.selectionStart);
|
let contentState = Modifier.replaceText(
|
||||||
var right = textarea.value.substring(textarea.selectionEnd);
|
this.state.editorState.getCurrentContent(),
|
||||||
if (right.length) {
|
this.state.editorState.getSelection(),
|
||||||
left += payload.displayname;
|
payload.displayname
|
||||||
}
|
);
|
||||||
else {
|
this.setState({
|
||||||
left = left.replace(/( ?)$/, " " + payload.displayname);
|
editorState: EditorState.push(this.state.editorState, contentState, 'insert-characters')
|
||||||
}
|
});
|
||||||
textarea.value = left + right;
|
editor.focus();
|
||||||
textarea.focus();
|
|
||||||
textarea.setSelectionRange(left.length, left.length);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
textarea.value = payload.displayname + ": ";
|
|
||||||
textarea.focus();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(ev) {
|
onKeyDown(ev) {
|
||||||
if (ev.keyCode === KeyCode.ENTER && !ev.shiftKey) {
|
if (ev.keyCode === KeyCode.UP || ev.keyCode === KeyCode.DOWN) {
|
||||||
var input = this.refs.textarea.value;
|
|
||||||
if (input.length === 0) {
|
|
||||||
ev.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sentHistory.push(input);
|
|
||||||
this.onEnter(ev);
|
|
||||||
}
|
|
||||||
else if (ev.keyCode === KeyCode.UP || ev.keyCode === KeyCode.DOWN) {
|
|
||||||
var oldSelectionStart = this.refs.textarea.selectionStart;
|
var oldSelectionStart = this.refs.textarea.selectionStart;
|
||||||
// Remember the keyCode because React will recycle the synthetic event
|
// Remember the keyCode because React will recycle the synthetic event
|
||||||
var keyCode = ev.keyCode;
|
var keyCode = ev.keyCode;
|
||||||
|
@ -222,48 +249,9 @@ export default class MessageComposerInput extends React.Component {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.refs.textarea.selectionStart == oldSelectionStart) {
|
if (this.refs.textarea.selectionStart == oldSelectionStart) {
|
||||||
this.sentHistory.next(keyCode === KeyCode.UP ? 1 : -1);
|
this.sentHistory.next(keyCode === KeyCode.UP ? 1 : -1);
|
||||||
this.resizeInput();
|
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.tabComplete) {
|
|
||||||
this.props.tabComplete.onKeyDown(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
setTimeout(function() {
|
|
||||||
if (self.refs.textarea && self.refs.textarea.value != '') {
|
|
||||||
self.onTypingActivity();
|
|
||||||
} else {
|
|
||||||
self.onFinishedTyping();
|
|
||||||
}
|
|
||||||
}, 10); // XXX: what is this 10ms setTimeout doing? Looks hacky :(
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeInput() {
|
|
||||||
console.error('fixme');
|
|
||||||
// scrollHeight is at least equal to clientHeight, so we have to
|
|
||||||
// temporarily crimp clientHeight to 0 to get an accurate scrollHeight value
|
|
||||||
// this.refs.textarea.style.height = "20px"; // 20 hardcoded from CSS
|
|
||||||
// var newHeight = Math.min(this.refs.textarea.scrollHeight,
|
|
||||||
// this.constructor.MAX_HEIGHT);
|
|
||||||
// this.refs.textarea.style.height = Math.ceil(newHeight) + "px";
|
|
||||||
// this.oldScrollHeight = this.refs.textarea.scrollHeight;
|
|
||||||
//
|
|
||||||
// if (this.props.onResize) {
|
|
||||||
// // kick gemini-scrollbar to re-layout
|
|
||||||
// this.props.onResize();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyUp(ev) {
|
|
||||||
if (this.refs.textarea.scrollHeight !== this.oldScrollHeight ||
|
|
||||||
ev.keyCode === KeyCode.DELETE ||
|
|
||||||
ev.keyCode === KeyCode.BACKSPACE)
|
|
||||||
{
|
|
||||||
this.resizeInput();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnter(ev) {
|
onEnter(ev) {
|
||||||
|
@ -271,24 +259,24 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
// bodge for now to set markdown state on/off. We probably want a separate
|
// bodge for now to set markdown state on/off. We probably want a separate
|
||||||
// area for "local" commands which don't hit out to the server.
|
// area for "local" commands which don't hit out to the server.
|
||||||
if (contentText.indexOf("/markdown") === 0) {
|
// if (contentText.indexOf("/markdown") === 0) {
|
||||||
ev.preventDefault();
|
// ev.preventDefault();
|
||||||
this.refs.textarea.value = '';
|
// this.refs.textarea.value = '';
|
||||||
if (contentText.indexOf("/markdown on") === 0) {
|
// if (contentText.indexOf("/markdown on") === 0) {
|
||||||
this.markdownEnabled = true;
|
// this.markdownEnabled = true;
|
||||||
}
|
// }
|
||||||
else if (contentText.indexOf("/markdown off") === 0) {
|
// else if (contentText.indexOf("/markdown off") === 0) {
|
||||||
this.markdownEnabled = false;
|
// this.markdownEnabled = false;
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
// const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
// Modal.createDialog(ErrorDialog, {
|
||||||
title: "Unknown command",
|
// title: "Unknown command",
|
||||||
description: "Usage: /markdown on|off"
|
// description: "Usage: /markdown on|off"
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
var cmd = SlashCommands.processInput(this.props.room.roomId, contentText);
|
var cmd = SlashCommands.processInput(this.props.room.roomId, contentText);
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
|
@ -341,17 +329,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
MatrixClientPeg.get().sendTextMessage(this.props.room.roomId, contentText);
|
MatrixClientPeg.get().sendTextMessage(this.props.room.roomId, contentText);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessagePromise.done(function() {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'message_sent'
|
|
||||||
});
|
|
||||||
}, function() {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'message_send_failed'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.refs.textarea.value = '';
|
this.refs.textarea.value = '';
|
||||||
this.resizeInput();
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,7 +415,28 @@ export default class MessageComposerInput extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyCommand(command) {
|
handleKeyCommand(command) {
|
||||||
const newState = RichUtils.handleKeyCommand(this.state.editorState, command);
|
if(command === 'toggle-mode') {
|
||||||
|
this.setState({
|
||||||
|
isRichtextEnabled: !this.state.isRichtextEnabled
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!this.state.isRichtextEnabled) {
|
||||||
|
let html = mdownToHtml(this.state.editorState.getCurrentContent().getPlainText());
|
||||||
|
this.setState({
|
||||||
|
editorState: this.createEditorState(HTMLtoContentState(html))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let markdown = stateToMarkdown(this.state.editorState.getCurrentContent());
|
||||||
|
let contentState = ContentState.createFromText(markdown);
|
||||||
|
this.setState({
|
||||||
|
editorState: this.createEditorState(contentState)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newState = RichUtils.handleKeyCommand(this.state.editorState, command);
|
||||||
if (newState) {
|
if (newState) {
|
||||||
this.onChange(newState);
|
this.onChange(newState);
|
||||||
return true;
|
return true;
|
||||||
|
@ -452,31 +452,50 @@ export default class MessageComposerInput extends React.Component {
|
||||||
if(!contentState.hasText())
|
if(!contentState.hasText())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const contentText = contentState.getPlainText(),
|
let contentText = contentState.getPlainText(), contentHTML;
|
||||||
contentHTML = contentStateToHTML(contentState);
|
|
||||||
|
|
||||||
MatrixClientPeg.get().sendHtmlMessage(this.props.room.roomId, contentText, contentHTML);
|
if(this.state.isRichtextEnabled) {
|
||||||
|
contentHTML = contentStateToHTML(contentState);
|
||||||
|
} else {
|
||||||
|
contentHTML = mdownToHtml(contentText);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sentHistory.push(contentHTML);
|
||||||
|
let sendMessagePromise = MatrixClientPeg.get().sendHtmlMessage(this.props.room.roomId, contentText, contentHTML);
|
||||||
|
|
||||||
|
sendMessagePromise.done(() => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'message_sent'
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'message_send_failed'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
editorState: EditorState.createEmpty(getScopedDecorator(this.props))
|
editorState: this.createEditorState()
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const containerStyle = {
|
let className = "mx_MessageComposer_input";
|
||||||
overflow: 'auto'
|
|
||||||
};
|
if(this.state.isRichtextEnabled) {
|
||||||
|
className += " mx_MessageComposer_input_rte"; // placeholder indicator for RTE mode
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MessageComposer_input"
|
<div className={className}
|
||||||
onClick={ this.onInputClick }
|
onClick={ this.onInputClick }>
|
||||||
style={containerStyle}>
|
|
||||||
<Editor ref="editor"
|
<Editor ref="editor"
|
||||||
placeholder="Type a message…"
|
placeholder="Type a message…"
|
||||||
editorState={this.state.editorState}
|
editorState={this.state.editorState}
|
||||||
onChange={(state) => this.onChange(state)}
|
onChange={(state) => this.onChange(state)}
|
||||||
|
keyBindingFn={MessageComposerInput.getKeyBinding}
|
||||||
handleKeyCommand={(command) => this.handleKeyCommand(command)}
|
handleKeyCommand={(command) => this.handleKeyCommand(command)}
|
||||||
handleReturn={ev => this.handleReturn(ev)} />
|
handleReturn={ev => this.handleReturn(ev)} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -494,6 +513,3 @@ MessageComposerInput.propTypes = {
|
||||||
// js-sdk Room object
|
// js-sdk Room object
|
||||||
room: React.PropTypes.object.isRequired
|
room: React.PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
// the height we limit the composer to
|
|
||||||
MessageComposerInput.MAX_HEIGHT = 100;
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue