Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit df69d1d

Browse files
authored
Add basic Markdown highlighting
Add basic Markdown highlighting
2 parents c0d7629 + e75a28b commit df69d1d

File tree

2 files changed

+75
-27
lines changed

2 files changed

+75
-27
lines changed

src/RichText.js

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ const STYLES = {
2121
UNDERLINE: 'u'
2222
};
2323

24+
const MARKDOWN_REGEX = {
25+
LINK: /(?:\[([^\]]+)\]\(([^\)]+)\))|\<(\w+:\/\/[^\>]+)\>/g,
26+
ITALIC: /([\*_])([\w\s]+?)\1/g,
27+
BOLD: /([\*_])\1([\w\s]+?)\1\1/g
28+
};
29+
30+
const USERNAME_REGEX = /@\S+:\S+/g;
31+
const ROOM_REGEX = /#\S+:\S+/g;
32+
2433
export function contentStateToHTML(contentState: ContentState): string {
2534
return contentState.getBlockMap().map((block) => {
2635
let elem = BLOCK_RENDER_MAP.get(block.getType()).element;
@@ -46,13 +55,10 @@ export function HTMLtoContentState(html: string): ContentState {
4655
return ContentState.createFromBlockArray(convertFromHTML(html));
4756
}
4857

49-
const USERNAME_REGEX = /@\S+:\S+/g;
50-
const ROOM_REGEX = /#\S+:\S+/g;
51-
5258
/**
5359
* Returns a composite decorator which has access to provided scope.
5460
*/
55-
export function getScopedDecorator(scope: any): CompositeDecorator {
61+
export function getScopedRTDecorators(scope: any): CompositeDecorator {
5662
let MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
5763

5864
let usernameDecorator = {
@@ -78,7 +84,34 @@ export function getScopedDecorator(scope: any): CompositeDecorator {
7884
}
7985
};
8086

81-
return new CompositeDecorator([usernameDecorator, roomDecorator]);
87+
return [usernameDecorator, roomDecorator];
88+
}
89+
90+
export function getScopedMDDecorators(scope: any): CompositeDecorator {
91+
let markdownDecorators = ['BOLD', 'ITALIC'].map(
92+
(style) => ({
93+
strategy: (contentBlock, callback) => {
94+
return findWithRegex(MARKDOWN_REGEX[style], contentBlock, callback);
95+
},
96+
component: (props) => (
97+
<span className={"mx_MarkdownElement mx_Markdown_" + style}>
98+
{props.children}
99+
</span>
100+
)
101+
}));
102+
103+
markdownDecorators.push({
104+
strategy: (contentBlock, callback) => {
105+
return findWithRegex(MARKDOWN_REGEX.LINK, contentBlock, callback);
106+
},
107+
component: (props) => (
108+
<a href="#" className="mx_MarkdownElement mx_Markdown_LINK">
109+
{props.children}
110+
</a>
111+
)
112+
});
113+
114+
return markdownDecorators;
82115
}
83116

84117
/**
@@ -97,15 +130,27 @@ function findWithRegex(regex, contentBlock: ContentBlock, callback: (start: numb
97130
/**
98131
* Passes rangeToReplace to modifyFn and replaces it in contentState with the result.
99132
*/
100-
export function modifyText(contentState: ContentState, rangeToReplace: SelectionState, modifyFn: (text: string) => string, ...rest): ContentState {
101-
let startKey = rangeToReplace.getStartKey(),
102-
endKey = contentState.getKeyAfter(rangeToReplace.getEndKey()),
133+
export function modifyText(contentState: ContentState, rangeToReplace: SelectionState,
134+
modifyFn: (text: string) => string, ...rest): ContentState {
135+
let getText = (key) => contentState.getBlockForKey(key).getText(),
136+
startKey = rangeToReplace.getStartKey(),
137+
startOffset = rangeToReplace.getStartOffset(),
138+
endKey = rangeToReplace.getEndKey(),
139+
endOffset = rangeToReplace.getEndOffset(),
103140
text = "";
104141

105-
for(let currentKey = startKey; currentKey && currentKey !== endKey; currentKey = contentState.getKeyAfter(currentKey)) {
106-
let currentBlock = contentState.getBlockForKey(currentKey);
107-
text += currentBlock.getText();
142+
143+
for(let currentKey = startKey;
144+
currentKey && currentKey !== endKey;
145+
currentKey = contentState.getKeyAfter(currentKey)) {
146+
text += getText(currentKey).substring(startOffset, blockText.length);
147+
148+
// from now on, we'll take whole blocks
149+
startOffset = 0;
108150
}
109151

152+
// add remaining part of last block
153+
text += getText(endKey).substring(startOffset, endOffset);
154+
110155
return Modifier.replaceText(contentState, rangeToReplace, modifyFn(text), ...rest);
111156
}

src/components/views/rooms/MessageComposerInput.js

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,16 @@ export default class MessageComposerInput extends React.Component {
9797
* - whether we've got rich text mode enabled
9898
* - contentState was passed in
9999
*/
100-
createEditorState(contentState: ?ContentState): EditorState {
101-
let func = contentState ? EditorState.createWithContent : EditorState.createEmpty;
102-
let args = contentState ? [contentState] : [];
103-
if(this.state.isRichtextEnabled) {
104-
args.push(RichText.getScopedDecorator(this.props));
100+
createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
101+
let decorators = richText ? RichText.getScopedRTDecorators(this.props) :
102+
RichText.getScopedMDDecorators(this.props),
103+
compositeDecorator = new CompositeDecorator(decorators);
104+
105+
if (contentState) {
106+
return EditorState.createWithContent(contentState, compositeDecorator);
107+
} else {
108+
return EditorState.createEmpty(compositeDecorator);
105109
}
106-
return func(...args);
107110
}
108111

109112
componentWillMount() {
@@ -194,7 +197,7 @@ export default class MessageComposerInput extends React.Component {
194197
if (contentJSON) {
195198
let content = convertFromRaw(JSON.parse(contentJSON));
196199
component.setState({
197-
editorState: component.createEditorState(content)
200+
editorState: component.createEditorState(this.state.isRichtextEnabled, content)
198201
});
199202
}
200203
}
@@ -341,22 +344,22 @@ export default class MessageComposerInput extends React.Component {
341344
}
342345

343346
enableRichtext(enabled: boolean) {
344-
this.setState({
345-
isRichtextEnabled: enabled
346-
});
347-
348-
if(!this.state.isRichtextEnabled) {
347+
if (enabled) {
349348
let html = mdownToHtml(this.state.editorState.getCurrentContent().getPlainText());
350349
this.setState({
351-
editorState: this.createEditorState(RichText.HTMLtoContentState(html))
350+
editorState: this.createEditorState(enabled, RichText.HTMLtoContentState(html))
352351
});
353352
} else {
354-
let markdown = stateToMarkdown(this.state.editorState.getCurrentContent());
355-
let contentState = ContentState.createFromText(markdown);
353+
let markdown = stateToMarkdown(this.state.editorState.getCurrentContent()),
354+
contentState = ContentState.createFromText(markdown);
356355
this.setState({
357-
editorState: this.createEditorState(contentState)
356+
editorState: this.createEditorState(enabled, contentState)
358357
});
359358
}
359+
360+
this.setState({
361+
isRichtextEnabled: enabled
362+
});
360363
}
361364

362365
handleKeyCommand(command: string): boolean {

0 commit comments

Comments
 (0)