Add basic Markdown highlighting

Add basic Markdown highlighting
This commit is contained in:
Aviral Dasgupta 2016-06-12 04:47:05 +05:30 committed by GitHub
commit df69d1de44
2 changed files with 79 additions and 31 deletions

View file

@ -21,6 +21,15 @@ const STYLES = {
UNDERLINE: 'u' 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 { export function contentStateToHTML(contentState: ContentState): string {
return contentState.getBlockMap().map((block) => { return contentState.getBlockMap().map((block) => {
let elem = BLOCK_RENDER_MAP.get(block.getType()).element; let elem = BLOCK_RENDER_MAP.get(block.getType()).element;
@ -46,13 +55,10 @@ export function HTMLtoContentState(html: string): ContentState {
return ContentState.createFromBlockArray(convertFromHTML(html)); 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. * 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 MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
let usernameDecorator = { 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) => (
<span className={"mx_MarkdownElement mx_Markdown_" + style}>
{props.children}
</span>
)
}));
markdownDecorators.push({
strategy: (contentBlock, callback) => {
return findWithRegex(MARKDOWN_REGEX.LINK, contentBlock, callback);
},
component: (props) => (
<a href="#" className="mx_MarkdownElement mx_Markdown_LINK">
{props.children}
</a>
)
});
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. * 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 { export function modifyText(contentState: ContentState, rangeToReplace: SelectionState,
let startKey = rangeToReplace.getStartKey(), modifyFn: (text: string) => string, ...rest): ContentState {
endKey = contentState.getKeyAfter(rangeToReplace.getEndKey()), let getText = (key) => contentState.getBlockForKey(key).getText(),
startKey = rangeToReplace.getStartKey(),
startOffset = rangeToReplace.getStartOffset(),
endKey = rangeToReplace.getEndKey(),
endOffset = rangeToReplace.getEndOffset(),
text = ""; text = "";
for(let currentKey = startKey; currentKey && currentKey !== endKey; currentKey = contentState.getKeyAfter(currentKey)) {
let currentBlock = contentState.getBlockForKey(currentKey); for(let currentKey = startKey;
text += currentBlock.getText(); 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); return Modifier.replaceText(contentState, rangeToReplace, modifyFn(text), ...rest);
} }

View file

@ -97,13 +97,16 @@ export default class MessageComposerInput extends React.Component {
* - whether we've got rich text mode enabled * - whether we've got rich text mode enabled
* - contentState was passed in * - contentState was passed in
*/ */
createEditorState(contentState: ?ContentState): EditorState { createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
let func = contentState ? EditorState.createWithContent : EditorState.createEmpty; let decorators = richText ? RichText.getScopedRTDecorators(this.props) :
let args = contentState ? [contentState] : []; RichText.getScopedMDDecorators(this.props),
if(this.state.isRichtextEnabled) { compositeDecorator = new CompositeDecorator(decorators);
args.push(RichText.getScopedDecorator(this.props));
if (contentState) {
return EditorState.createWithContent(contentState, compositeDecorator);
} else {
return EditorState.createEmpty(compositeDecorator);
} }
return func(...args);
} }
componentWillMount() { componentWillMount() {
@ -194,7 +197,7 @@ export default class MessageComposerInput extends React.Component {
if (contentJSON) { if (contentJSON) {
let content = convertFromRaw(JSON.parse(contentJSON)); let content = convertFromRaw(JSON.parse(contentJSON));
component.setState({ 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) { 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({ this.setState({
isRichtextEnabled: enabled 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 { handleKeyCommand(command: string): boolean {