diff --git a/src/RichText.js b/src/RichText.js
index f1f2188d0d..c0d80d2ec7 100644
--- a/src/RichText.js
+++ b/src/RichText.js
@@ -21,6 +21,15 @@ const STYLES = {
UNDERLINE: 'u'
};
+const MARKDOWN_REGEX = {
+ LINK: /(?:\[([^\]]+)\]\(([^\)]+)\))|\<(\w+:\/\/[^\>]+)\>/g,
+ ITALIC: /([\*_])([\w\s]+?)\1/g,
+ BOLD: /([\*_])\1([\w\s]+?)\1\1/g
+};
+
+const USERNAME_REGEX = /@\S+:\S+/g;
+const ROOM_REGEX = /#\S+:\S+/g;
+
export function contentStateToHTML(contentState: ContentState): string {
return contentState.getBlockMap().map((block) => {
let elem = BLOCK_RENDER_MAP.get(block.getType()).element;
@@ -46,13 +55,10 @@ export function HTMLtoContentState(html: string): ContentState {
return ContentState.createFromBlockArray(convertFromHTML(html));
}
-const USERNAME_REGEX = /@\S+:\S+/g;
-const ROOM_REGEX = /#\S+:\S+/g;
-
/**
* Returns a composite decorator which has access to provided scope.
*/
-export function getScopedDecorator(scope: any): CompositeDecorator {
+export function getScopedRTDecorators(scope: any): CompositeDecorator {
let MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
let usernameDecorator = {
@@ -78,7 +84,34 @@ export function getScopedDecorator(scope: any): CompositeDecorator {
}
};
- return new CompositeDecorator([usernameDecorator, roomDecorator]);
+ return [usernameDecorator, roomDecorator];
+}
+
+export function getScopedMDDecorators(scope: any): CompositeDecorator {
+ let markdownDecorators = ['BOLD', 'ITALIC'].map(
+ (style) => ({
+ strategy: (contentBlock, callback) => {
+ return findWithRegex(MARKDOWN_REGEX[style], contentBlock, callback);
+ },
+ component: (props) => (
+
+ {props.children}
+
+ )
+ }));
+
+ markdownDecorators.push({
+ strategy: (contentBlock, callback) => {
+ return findWithRegex(MARKDOWN_REGEX.LINK, contentBlock, callback);
+ },
+ component: (props) => (
+
+ {props.children}
+
+ )
+ });
+
+ return markdownDecorators;
}
/**
@@ -97,15 +130,27 @@ function findWithRegex(regex, contentBlock: ContentBlock, callback: (start: numb
/**
* Passes rangeToReplace to modifyFn and replaces it in contentState with the result.
*/
-export function modifyText(contentState: ContentState, rangeToReplace: SelectionState, modifyFn: (text: string) => string, ...rest): ContentState {
- let startKey = rangeToReplace.getStartKey(),
- endKey = contentState.getKeyAfter(rangeToReplace.getEndKey()),
+export function modifyText(contentState: ContentState, rangeToReplace: SelectionState,
+ modifyFn: (text: string) => string, ...rest): ContentState {
+ let getText = (key) => contentState.getBlockForKey(key).getText(),
+ startKey = rangeToReplace.getStartKey(),
+ startOffset = rangeToReplace.getStartOffset(),
+ endKey = rangeToReplace.getEndKey(),
+ endOffset = rangeToReplace.getEndOffset(),
text = "";
- for(let currentKey = startKey; currentKey && currentKey !== endKey; currentKey = contentState.getKeyAfter(currentKey)) {
- let currentBlock = contentState.getBlockForKey(currentKey);
- text += currentBlock.getText();
+
+ for(let currentKey = startKey;
+ currentKey && currentKey !== endKey;
+ currentKey = contentState.getKeyAfter(currentKey)) {
+ text += getText(currentKey).substring(startOffset, blockText.length);
+
+ // from now on, we'll take whole blocks
+ startOffset = 0;
}
+ // add remaining part of last block
+ text += getText(endKey).substring(startOffset, endOffset);
+
return Modifier.replaceText(contentState, rangeToReplace, modifyFn(text), ...rest);
}
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index 3c5ddf1512..da61631b4d 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -97,13 +97,16 @@ export default class MessageComposerInput extends React.Component {
* - 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(RichText.getScopedDecorator(this.props));
+ createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
+ let decorators = richText ? RichText.getScopedRTDecorators(this.props) :
+ RichText.getScopedMDDecorators(this.props),
+ compositeDecorator = new CompositeDecorator(decorators);
+
+ if (contentState) {
+ return EditorState.createWithContent(contentState, compositeDecorator);
+ } else {
+ return EditorState.createEmpty(compositeDecorator);
}
- return func(...args);
}
componentWillMount() {
@@ -194,7 +197,7 @@ export default class MessageComposerInput extends React.Component {
if (contentJSON) {
let content = convertFromRaw(JSON.parse(contentJSON));
component.setState({
- editorState: component.createEditorState(content)
+ editorState: component.createEditorState(this.state.isRichtextEnabled, content)
});
}
}
@@ -341,22 +344,22 @@ export default class MessageComposerInput extends React.Component {
}
enableRichtext(enabled: boolean) {
+ if (enabled) {
+ let html = mdownToHtml(this.state.editorState.getCurrentContent().getPlainText());
+ this.setState({
+ editorState: this.createEditorState(enabled, RichText.HTMLtoContentState(html))
+ });
+ } else {
+ let markdown = stateToMarkdown(this.state.editorState.getCurrentContent()),
+ contentState = ContentState.createFromText(markdown);
+ this.setState({
+ editorState: this.createEditorState(enabled, contentState)
+ });
+ }
+
this.setState({
isRichtextEnabled: enabled
});
-
- if(!this.state.isRichtextEnabled) {
- let html = mdownToHtml(this.state.editorState.getCurrentContent().getPlainText());
- this.setState({
- editorState: this.createEditorState(RichText.HTMLtoContentState(html))
- });
- } else {
- let markdown = stateToMarkdown(this.state.editorState.getCurrentContent());
- let contentState = ContentState.createFromText(markdown);
- this.setState({
- editorState: this.createEditorState(contentState)
- });
- }
}
handleKeyCommand(command: string): boolean {