Skip to content

Commit f7aa5af

Browse files
authored
Merge pull request #1594 from passimm/issue_1469
Issue #1469 - User mentions in the posts
2 parents da35477 + de69dff commit f7aa5af

File tree

4 files changed

+87
-15
lines changed

4 files changed

+87
-15
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"draft-js-image-plugin": "^2.0.0-rc2",
6363
"draft-js-link-plugin": "^1.2.2",
6464
"draft-js-plugins-editor": "^2.0.0-rc2",
65+
"draft-js-mention-plugin": "^2.0.0-rc2",
6566
"draft-js-utils": "^0.1.7",
6667
"fbemitter": "^2.1.1",
6768
"fbjs": "^0.8.12",

src/components/Feed/NewPost.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class NewPost extends React.Component {
1111
}
1212

1313
render() {
14-
const {currentUser, titlePlaceholder, isCreating, hasError} = this.props
14+
const {currentUser, allMembers, titlePlaceholder, isCreating, hasError} = this.props
1515
let authorName = currentUser.firstName
1616
if (authorName && currentUser.lastName) {
1717
authorName += ' ' + currentUser.lastName
@@ -33,6 +33,7 @@ class NewPost extends React.Component {
3333
hasError={hasError}
3434
avatarUrl={currentUser.photoURL}
3535
authorName={authorName}
36+
allMembers={allMembers}
3637
/>
3738
)
3839
}
@@ -41,6 +42,7 @@ class NewPost extends React.Component {
4142

4243
NewPost.propTypes = {
4344
currentUser: PropTypes.object.isRequired,
45+
allMembers: PropTypes.object.isRequired,
4446
onPost: PropTypes.func.isRequired,
4547
onNewPostChange: PropTypes.func.isRequired,
4648
hasError: PropTypes.bool,

src/components/RichTextArea/RichTextArea.jsx

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@ import stateToMarkdown from '../../helpers/stateToMarkdown'
1818
import 'draft-js-link-plugin/lib/plugin.css'
1919
import EditorIcons from './EditorIcons'
2020
import './RichTextArea.scss'
21+
import 'draft-js-mention-plugin/lib/plugin.css'
22+
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin'
23+
import _ from 'lodash'
2124

2225
const linkPlugin = createLinkPlugin()
2326
const blockDndPlugin = createBlockDndPlugin()
27+
const mentionPlugin = createMentionPlugin({mentionPrefix: '@'})
28+
2429
const decorator = composeDecorators(
2530
blockDndPlugin.decorator
2631
)
2732
const allowImages = false
28-
const plugins = [linkPlugin, blockDndPlugin]
33+
const plugins = [linkPlugin, blockDndPlugin, mentionPlugin]
2934
if (allowImages){
3035
const imagePlugin = createImagePlugin({ decorator })
3136
plugins.push(handleDropPlugin)
@@ -51,7 +56,8 @@ const blocks = [
5156
class RichTextArea extends React.Component {
5257
constructor(props) {
5358
super(props)
54-
this.state = {editorExpanded: false, editorState: EditorState.createEmpty(), titleValue: ''}
59+
this.mentions = _.map(_.values(this.props.allMembers), (e) => { return {name: e.firstName + ' ' + e.lastName, handle: e.handle, userId: e.userId} })
60+
this.state = {editorExpanded: false, editorState: EditorState.createEmpty(), titleValue: '', suggestions: this.mentions, mentions: []}
5561
this.onTitleChange = this.onTitleChange.bind(this)
5662
this.onEditorChange = this.onEditorChange.bind(this)
5763
this.handleKeyCommand = this.handleKeyCommand.bind(this)
@@ -201,12 +207,42 @@ class RichTextArea extends React.Component {
201207
return
202208
}
203209
const title = this.state.titleValue
204-
const content = this.state.currentMDContent
210+
211+
var that = this
212+
const replaceMentionWithUserID = (content) =>
213+
{
214+
const encodeContent = (text) => {
215+
return text.replace(/[*_`]/g, '\\$&')
216+
}
217+
218+
const userIdMap = _.reduce(that.mentions, (obj, item) => {
219+
obj[encodeContent(item.name)] = encodeContent(item.handle)
220+
return obj
221+
}, {})
222+
223+
for (var item in userIdMap)
224+
{
225+
content = content.replace('@' + item, '@' + userIdMap[item])
226+
}
227+
return content
228+
}
229+
230+
const content = replaceMentionWithUserID(this.state.currentMDContent)
231+
205232
if ((this.props.disableTitle || title) && content) {
206233
this.props.onPost({title, content})
207234
}
208235
}
209236

237+
onSearchChange = ({ value }) => {
238+
this.setState({
239+
suggestions: defaultSuggestionsFilter(value, this.mentions),
240+
});
241+
};
242+
243+
onAddMention = (mention) => {
244+
}
245+
210246
cancelEdit() {
211247
this.props.cancelEdit()
212248
}
@@ -220,6 +256,7 @@ class RichTextArea extends React.Component {
220256
this.setState({uploading})
221257
}
222258
render() {
259+
const {MentionSuggestions} = mentionPlugin
223260
const {className, avatarUrl, authorName, titlePlaceholder, contentPlaceholder, editMode, isCreating, isGettingComment, disableTitle} = this.props
224261
const {editorExpanded, editorState, titleValue, oldMDContent, currentMDContent, uploading} = this.state
225262
let canSubmit = (disableTitle || titleValue.trim())
@@ -232,6 +269,28 @@ class RichTextArea extends React.Component {
232269
const currentEntity = getCurrentEntity(editorState)
233270
const disableForCodeBlock = blockType === 'code-block'
234271

272+
const Entry = (props) => {
273+
const {
274+
mention,
275+
theme,
276+
searchValue, // eslint-disable-line no-unused-vars
277+
isFocused, // eslint-disable-line no-unused-vars
278+
...parentProps
279+
} = props;
280+
281+
return (
282+
<div {...parentProps}>
283+
<div className={theme.mentionSuggestionsEntryContainer}>
284+
<div className={theme.mentionSuggestionsEntryContainerRight}>
285+
<div className={theme.mentionSuggestionsEntryText}>
286+
{mention.get('handle')}
287+
</div>
288+
</div>
289+
</div>
290+
</div>
291+
);
292+
};
293+
235294
return (
236295
<div className={cn(className, 'rich-editor', {expanded: editorExpanded || editMode})} ref="richEditor">
237296
{(isCreating || isGettingComment) &&
@@ -258,15 +317,23 @@ class RichTextArea extends React.Component {
258317
/>
259318
<div className="draftjs-editor tc-textarea">
260319
{!isGettingComment &&
261-
<Editor
262-
ref="editor"
263-
placeholder={contentPlaceholder}
264-
editorState={editorState}
265-
onChange={this.onEditorChange}
266-
handleKeyCommand={this.handleKeyCommand}
267-
plugins={plugins}
268-
setUploadState={this.setUploadState}
269-
/>
320+
<div>
321+
<Editor
322+
ref="editor"
323+
placeholder={contentPlaceholder}
324+
editorState={editorState}
325+
onChange={this.onEditorChange}
326+
handleKeyCommand={this.handleKeyCommand}
327+
plugins={plugins}
328+
setUploadState={this.setUploadState}
329+
/>
330+
<MentionSuggestions
331+
onSearchChange={this.onSearchChange}
332+
suggestions={this.state.suggestions}
333+
onAddMention={this.onAddMention}
334+
entryComponent={Entry}
335+
/>
336+
</div>
270337
}
271338
<div className="textarea-footer">
272339
<div className="textarea-buttons">
@@ -358,7 +425,8 @@ RichTextArea.propTypes = {
358425
oldTitle: PropTypes.string,
359426
oldContent: PropTypes.string,
360427
title: PropTypes.string,
361-
content: PropTypes.string
428+
content: PropTypes.string,
429+
allMembers: PropTypes.object
362430
}
363431

364432
export default RichTextArea

src/projects/detail/containers/FeedContainer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ class FeedView extends React.Component {
338338
}
339339

340340
render () {
341-
const {currentUser, project, currentMemberRole, isCreatingFeed, error } = this.props
341+
const {currentUser, project, currentMemberRole, isCreatingFeed, error, allMembers} = this.props
342342
const { feeds } = this.state
343343
const showDraftSpec = project.status === PROJECT_STATUS_DRAFT && currentMemberRole === PROJECT_ROLE_CUSTOMER
344344
const onLeaveMessage = this.onLeave() || ''
@@ -383,6 +383,7 @@ class FeedView extends React.Component {
383383
/>
384384
<NewPost
385385
currentUser={currentUser}
386+
allMembers={allMembers}
386387
onPost={this.onNewPost}
387388
isCreating={isCreatingFeed}
388389
hasError={error}

0 commit comments

Comments
 (0)