From 7b7e73fb9290a8084989a4219681c636670e7555 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 29 Jun 2015 14:37:59 -0400 Subject: added team-wide and channel-wide mentions --- api/post.go | 14 ++++++++-- model/user.go | 11 +++++--- web/react/components/user_settings.jsx | 47 ++++++++++++++++++++++++++++++---- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/api/post.go b/api/post.go index 99cbdcb85..2d812e8de 100644 --- a/api/post.go +++ b/api/post.go @@ -273,7 +273,7 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { } else { - // Find out who is a member of the channel only keep those profiles + // Find out who is a member of the channel, only keep those profiles if eResult := <-echan; eResult.Err != nil { l4g.Error("Failed to get channel members channel_id=%v err=%v", post.ChannelId, eResult.Err.Message) return @@ -306,13 +306,23 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { } } } + + // Add @all to keywords if user has them turned on + if profile.NotifyProps["all"] == "true" { + keywordMap["@all"] = append(keywordMap["@all"], profile.Id) + } + + // Add @channel to keywords if user has them turned on + if profile.NotifyProps["channel"] == "true" { + keywordMap["@channel"] = append(keywordMap["@channel"], profile.Id) + } } // Build a map as a list of unique user_ids that are mentioned in this post splitF := func(c rune) bool { return model.SplitRunes[c] } - splitMessage := strings.FieldsFunc(strings.Replace(post.Message, "
", " ", -1), splitF) + splitMessage := strings.FieldsFunc(post.Message, splitF) for _, word := range splitMessage { // Non-case-sensitive check for regular keys diff --git a/model/user.go b/model/user.go index 794adcad4..ca62f3d72 100644 --- a/model/user.go +++ b/model/user.go @@ -148,9 +148,12 @@ func (u *User) SetDefaultNotifications() { u.NotifyProps["desktop"] = USER_NOTIFY_ALL u.NotifyProps["desktop_sound"] = "true" u.NotifyProps["mention_keys"] = u.Username - u.NotifyProps["first_name"] = "true" + u.NotifyProps["first_name"] = "false" + u.NotifyProps["all"] = "true" + u.NotifyProps["channel"] = "true" splitName := strings.Split(u.FullName, " ") if len(splitName) > 0 && splitName[0] != "" { + u.NotifyProps["first_name"] = "true" u.NotifyProps["mention_keys"] += "," + splitName[0] } } @@ -277,17 +280,17 @@ func ComparePassword(hash string, password string) bool { func IsUsernameValid(username string) bool { - var restrictedUsernames = []string { + var restrictedUsernames = []string{ BOT_USERNAME, "all", "channel", } - for _,restrictedUsername := range restrictedUsernames { + for _, restrictedUsername := range restrictedUsernames { if username == restrictedUsername { return false } - } + } return true } diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 147973fb7..f97a06db3 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -20,11 +20,10 @@ function getNotificationsStateFromStores() { var mention_key = false; var custom_keys = ""; var first_name_key = false; + var all_key = false; + var channel_key = false; - if (!user.notify_props) { - mention_keys = user.username; - if (user.full_name.length > 0) mention_keys += ","+ user.full_name.split(" ")[0]; - } else { + if (user.notify_props) { if (user.notify_props.mention_keys !== undefined) { var keys = user.notify_props.mention_keys.split(','); @@ -48,9 +47,17 @@ function getNotificationsStateFromStores() { if (user.notify_props.first_name !== undefined) { first_name_key = user.notify_props.first_name === "true"; } + + if (user.notify_props.all !== undefined) { + all_key = user.notify_props.all === "true"; + } + + if (user.notify_props.channel !== undefined) { + channel_key = user.notify_props.channel === "true"; + } } - return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key }; + return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key }; } @@ -73,6 +80,8 @@ var NotificationsTab = React.createClass({ data["mention_keys"] = string_keys; data["first_name"] = this.state.first_name_key ? "true" : "false"; + data["all"] = this.state.all_key ? "true" : "false"; + data["channel"] = this.state.channel_key ? "true" : "false"; client.updateUserNotifyProps(data, function(data) { @@ -120,6 +129,12 @@ var NotificationsTab = React.createClass({ updateFirstNameKey: function(val) { this.setState({ first_name_key: val }); }, + updateAllKey: function(val) { + this.setState({ all_key: val }); + }, + updateChannelKey: function(val) { + this.setState({ channel_key: val }); + }, updateCustomMentionKeys: function() { var checked = this.refs.customcheck.getDOMNode().checked; @@ -339,6 +354,26 @@ var NotificationsTab = React.createClass({ ); + inputs.push( +
+
+ +
+
+ ); + + inputs.push( +
+
+ +
+
+ ); + inputs.push(
@@ -369,6 +404,8 @@ var NotificationsTab = React.createClass({ } if (this.state.username_key) keys.push(this.props.user.username); if (this.state.mention_key) keys.push('@'+this.props.user.username); + if (this.state.all_key) keys.push('@all'); + if (this.state.channel_key) keys.push('@channel'); if (this.state.custom_keys.length > 0) keys = keys.concat(this.state.custom_keys.split(',')); var describe = ""; -- cgit v1.2.3-1-g7c22 From 2d3ef0b0510e4c0be49cb6025e312fcb932789fd Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 29 Jun 2015 15:20:49 -0400 Subject: @all and @channel now auto-complete --- web/react/components/mention.jsx | 2 +- web/react/components/mention_list.jsx | 15 ++++++++++++++- web/react/components/textbox.jsx | 2 +- web/react/utils/constants.jsx | 1 + 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index 86a423138..f1292c743 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -9,7 +9,7 @@ module.exports = React.createClass({ return (
- @{this.props.username}{this.props.name} + @{this.props.username}{this.props.secondary_text}
); } diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index 8b7e25b04..eb21e0efe 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -74,6 +74,18 @@ module.exports = React.createClass({ users.push(profiles[id]); } + var all = {}; + all.username = "all"; + all.full_name = ""; + all.secondary_text = "Notifies everyone in the team"; + users.push(all); + + var channel = {}; + channel.username = "channel"; + channel.full_name = ""; + channel.secondary_text = "Notifies everyone in the channel"; + users.push(channel); + users.sort(function(a,b) { if (a.username < b.username) return -1; if (a.username > b.username) return 1; @@ -91,6 +103,7 @@ module.exports = React.createClass({ var splitName = users[i].full_name.split(' '); firstName = splitName[0].toLowerCase(); lastName = splitName.length > 1 ? splitName[splitName.length-1].toLowerCase() : ""; + users[i].secondary_text = users[i].full_name; } if (firstName.lastIndexOf(mentionText,0) === 0 @@ -99,7 +112,7 @@ module.exports = React.createClass({ ); diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index 7a4762e07..934e863a2 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -153,7 +153,7 @@ module.exports = React.createClass({ var mentions = []; for (var i = 0; i < matches.length; i++) { var m = matches[i].substring(1,matches[i].length).trim(); - if (m in profileMap && mentions.indexOf(m) === -1) { + if ((m in profileMap && mentions.indexOf(m) === -1) || Constants.SPECIAL_MENTIONS.indexOf(m) !== -1) { mentions.push(m); } } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index e5f42c8a0..f3f3399d9 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -36,6 +36,7 @@ module.exports = { SERVER_ACTION: null, VIEW_ACTION: null }), + SPECIAL_MENTIONS: ['all', 'channel'], CHARACTER_LIMIT: 4000, IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png'], AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'], -- cgit v1.2.3-1-g7c22 From 495b0e45235b646e7bb6aedf53004ee359b6887f Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 30 Jun 2015 08:42:08 -0400 Subject: add default icon for non-user autocompletes --- web/react/components/mention.jsx | 10 ++++++++-- web/sass-files/sass/partials/_mentions.scss | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index f1292c743..3c33ddf49 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -6,10 +6,16 @@ module.exports = React.createClass({ this.props.handleClick(this.props.username); }, render: function() { + var icon; + if (this.props.id != null) { + icon = ; + } else { + icon = ; + } return (
- - @{this.props.username}{this.props.secondary_text} +
{icon}
+
@{this.props.username}{this.props.secondary_text}
); } diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index cb6ff16c5..ee254b546 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -56,3 +56,8 @@ .mention-link { color:$primary-color; } + +.mention-align { + position:relative; + top:5px; +} -- cgit v1.2.3-1-g7c22 From f030476ff5f438b96d7282c37ad7a42ffc61ae85 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 30 Jun 2015 09:06:10 -0400 Subject: @all and @channel now highlight like other mentions --- web/react/stores/user_store.jsx | 3 +++ web/react/utils/utils.jsx | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index bbca92c84..e1df4879f 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -240,6 +240,9 @@ var UserStore = assign({}, EventEmitter.prototype, { if (first.length > 0) keys.push(first); } + if (user.notify_props.all === "true") keys.push('@all'); + if (user.notify_props.channel === "true") keys.push('@channel'); + return keys; } else { return []; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 75c583c8f..d50a044bc 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -421,10 +421,13 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - if (explicitMention && UserStore.getProfileByUsername(explicitMention[1])) { + if (explicitMention && + (UserStore.getProfileByUsername(explicitMention[1]) || + Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) + { var name = explicitMention[1]; // do both a non-case sensitive and case senstive check - var mClass = (name.toLowerCase() in implicitKeywords || name in implicitKeywords) ? mentionClass : ""; + var mClass = (('@'+name.toLowerCase()) in implicitKeywords || ('@'+name) in implicitKeywords) ? mentionClass : ""; var suffix = word.match(puncEndRegex); var prefix = word.match(puncStartRegex); -- cgit v1.2.3-1-g7c22 From 8be4df00b4d30c220d6f0d13734501c8c94e6495 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 30 Jun 2015 09:11:06 -0400 Subject: added mentioned username to default mention keys --- model/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/user.go b/model/user.go index ca62f3d72..18fbb0d2a 100644 --- a/model/user.go +++ b/model/user.go @@ -147,7 +147,7 @@ func (u *User) SetDefaultNotifications() { u.NotifyProps["email"] = "true" u.NotifyProps["desktop"] = USER_NOTIFY_ALL u.NotifyProps["desktop_sound"] = "true" - u.NotifyProps["mention_keys"] = u.Username + u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username u.NotifyProps["first_name"] = "false" u.NotifyProps["all"] = "true" u.NotifyProps["channel"] = "true" -- cgit v1.2.3-1-g7c22