Add basic Markdown highlighting
Add basic Markdown highlighting
This commit is contained in:
commit
df69d1de44
2 changed files with 79 additions and 31 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue