From a5a3e4e9150090e8bc2cc031b548125a56bf103a Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Sat, 11 Jun 2016 23:13:57 +0200 Subject: [PATCH 1/4] Basic Markdown highlighting --- src/RichText.js | 37 ++++++++++++++++++- .../views/rooms/MessageComposerInput.js | 16 ++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/RichText.js b/src/RichText.js index f1f2188d0d..2334bbf2d6 100644 --- a/src/RichText.js +++ b/src/RichText.js @@ -52,7 +52,7 @@ 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,9 +78,42 @@ 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; +} + +const MARKDOWN_REGEX = { + LINK: /(?:\[([^\]]+)\]\(([^\)]+)\))|\<(\w+:\/\/[^\>]+)\>/g, + ITALIC: /([\*_])([\w\s]+?)\1/g, + BOLD: /([\*_])\1([\w\s]+?)\1\1/g +}; + /** * Utility function that looks for regex matches within a ContentBlock and invokes {callback} with (start, end) * From https://facebook.github.io/draft-js/docs/advanced-topics-decorators.html diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 3c5ddf1512..eb096d5bc3 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -99,10 +99,10 @@ export default class MessageComposerInput extends React.Component { */ createEditorState(contentState: ?ContentState): EditorState { let func = contentState ? EditorState.createWithContent : EditorState.createEmpty; + const decoratorFunc = this.state.isRichtextEnabled ? RichText.getScopedRTDecorators : + RichText.getScopedMDDecorators; let args = contentState ? [contentState] : []; - if(this.state.isRichtextEnabled) { - args.push(RichText.getScopedDecorator(this.props)); - } + args.push(new CompositeDecorator(decoratorFunc(this.props))); return func(...args); } @@ -341,11 +341,7 @@ export default class MessageComposerInput extends React.Component { } enableRichtext(enabled: boolean) { - this.setState({ - isRichtextEnabled: enabled - }); - - if(!this.state.isRichtextEnabled) { + if(enabled) { let html = mdownToHtml(this.state.editorState.getCurrentContent().getPlainText()); this.setState({ editorState: this.createEditorState(RichText.HTMLtoContentState(html)) @@ -357,6 +353,10 @@ export default class MessageComposerInput extends React.Component { editorState: this.createEditorState(contentState) }); } + + this.setState({ + isRichtextEnabled: enabled + }); } handleKeyCommand(command: string): boolean { From 8f45f168d5224e56dc1cdb5cf83e5a498cbb76c6 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Sat, 11 Jun 2016 23:51:18 +0200 Subject: [PATCH 2/4] Fix highlighting behaviour on switch Hopefully made the code a little bit clearer. --- .../views/rooms/MessageComposerInput.js | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index eb096d5bc3..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; - const decoratorFunc = this.state.isRichtextEnabled ? RichText.getScopedRTDecorators : - RichText.getScopedMDDecorators; - let args = contentState ? [contentState] : []; - args.push(new CompositeDecorator(decoratorFunc(this.props))); - return func(...args); + 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); + } } 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,16 +344,16 @@ export default class MessageComposerInput extends React.Component { } enableRichtext(enabled: boolean) { - if(enabled) { + if (enabled) { let html = mdownToHtml(this.state.editorState.getCurrentContent().getPlainText()); this.setState({ - editorState: this.createEditorState(RichText.HTMLtoContentState(html)) + editorState: this.createEditorState(enabled, RichText.HTMLtoContentState(html)) }); } else { - let markdown = stateToMarkdown(this.state.editorState.getCurrentContent()); - let contentState = ContentState.createFromText(markdown); + let markdown = stateToMarkdown(this.state.editorState.getCurrentContent()), + contentState = ContentState.createFromText(markdown); this.setState({ - editorState: this.createEditorState(contentState) + editorState: this.createEditorState(enabled, contentState) }); } From 294a8efdc46c1c81a43fa103c391dd6eeb350972 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Sun, 12 Jun 2016 00:50:30 +0200 Subject: [PATCH 3/4] Fix behaviour of modifyText Since it was not correctly extracting the selected part of the text string --- src/RichText.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/RichText.js b/src/RichText.js index 2334bbf2d6..d218e698a6 100644 --- a/src/RichText.js +++ b/src/RichText.js @@ -130,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); } From e75a28bb0dea6b79895e5efe3c01631935d026d9 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Sun, 12 Jun 2016 00:52:30 +0200 Subject: [PATCH 4/4] Minimal house cleaning --- src/RichText.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/RichText.js b/src/RichText.js index d218e698a6..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,9 +55,6 @@ 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. */ @@ -108,12 +114,6 @@ export function getScopedMDDecorators(scope: any): CompositeDecorator { return markdownDecorators; } -const MARKDOWN_REGEX = { - LINK: /(?:\[([^\]]+)\]\(([^\)]+)\))|\<(\w+:\/\/[^\>]+)\>/g, - ITALIC: /([\*_])([\w\s]+?)\1/g, - BOLD: /([\*_])\1([\w\s]+?)\1\1/g -}; - /** * Utility function that looks for regex matches within a ContentBlock and invokes {callback} with (start, end) * From https://facebook.github.io/draft-js/docs/advanced-topics-decorators.html