Merge pull request #106 from mattermost/mm-1119
fixes mm-1119 adds @all and @channel mention capabilites
Этот коммит содержится в:
14
api/post.go
14
api/post.go
@@ -273,7 +273,7 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) {
|
|||||||
|
|
||||||
} else {
|
} 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 {
|
if eResult := <-echan; eResult.Err != nil {
|
||||||
l4g.Error("Failed to get channel members channel_id=%v err=%v", post.ChannelId, eResult.Err.Message)
|
l4g.Error("Failed to get channel members channel_id=%v err=%v", post.ChannelId, eResult.Err.Message)
|
||||||
return
|
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
|
// Build a map as a list of unique user_ids that are mentioned in this post
|
||||||
splitF := func(c rune) bool {
|
splitF := func(c rune) bool {
|
||||||
return model.SplitRunes[c]
|
return model.SplitRunes[c]
|
||||||
}
|
}
|
||||||
splitMessage := strings.FieldsFunc(strings.Replace(post.Message, "<br>", " ", -1), splitF)
|
splitMessage := strings.FieldsFunc(post.Message, splitF)
|
||||||
for _, word := range splitMessage {
|
for _, word := range splitMessage {
|
||||||
|
|
||||||
// Non-case-sensitive check for regular keys
|
// Non-case-sensitive check for regular keys
|
||||||
|
|||||||
@@ -147,10 +147,13 @@ func (u *User) SetDefaultNotifications() {
|
|||||||
u.NotifyProps["email"] = "true"
|
u.NotifyProps["email"] = "true"
|
||||||
u.NotifyProps["desktop"] = USER_NOTIFY_ALL
|
u.NotifyProps["desktop"] = USER_NOTIFY_ALL
|
||||||
u.NotifyProps["desktop_sound"] = "true"
|
u.NotifyProps["desktop_sound"] = "true"
|
||||||
u.NotifyProps["mention_keys"] = u.Username
|
u.NotifyProps["mention_keys"] = u.Username + ",@" + 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, " ")
|
splitName := strings.Split(u.FullName, " ")
|
||||||
if len(splitName) > 0 && splitName[0] != "" {
|
if len(splitName) > 0 && splitName[0] != "" {
|
||||||
|
u.NotifyProps["first_name"] = "true"
|
||||||
u.NotifyProps["mention_keys"] += "," + splitName[0]
|
u.NotifyProps["mention_keys"] += "," + splitName[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,17 +280,17 @@ func ComparePassword(hash string, password string) bool {
|
|||||||
|
|
||||||
func IsUsernameValid(username string) bool {
|
func IsUsernameValid(username string) bool {
|
||||||
|
|
||||||
var restrictedUsernames = []string {
|
var restrictedUsernames = []string{
|
||||||
BOT_USERNAME,
|
BOT_USERNAME,
|
||||||
"all",
|
"all",
|
||||||
"channel",
|
"channel",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _,restrictedUsername := range restrictedUsernames {
|
for _, restrictedUsername := range restrictedUsernames {
|
||||||
if username == restrictedUsername {
|
if username == restrictedUsername {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,16 @@ module.exports = React.createClass({
|
|||||||
this.props.handleClick(this.props.username);
|
this.props.handleClick(this.props.username);
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var icon;
|
||||||
|
if (this.props.id != null) {
|
||||||
|
icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/></span>;
|
||||||
|
} else {
|
||||||
|
icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mentions-name" onClick={this.handleClick}>
|
<div className="mentions-name" onClick={this.handleClick}>
|
||||||
<img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/>
|
<div className="pull-left">{icon}</div>
|
||||||
<span>@{this.props.username}</span><span className="mention-fullname">{this.props.name}</span>
|
<div className="pull-left mention-align"><span>@{this.props.username}</span><span className="mention-fullname">{this.props.secondary_text}</span></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,18 @@ module.exports = React.createClass({
|
|||||||
users.push(profiles[id]);
|
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) {
|
users.sort(function(a,b) {
|
||||||
if (a.username < b.username) return -1;
|
if (a.username < b.username) return -1;
|
||||||
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(' ');
|
var splitName = users[i].full_name.split(' ');
|
||||||
firstName = splitName[0].toLowerCase();
|
firstName = splitName[0].toLowerCase();
|
||||||
lastName = splitName.length > 1 ? splitName[splitName.length-1].toLowerCase() : "";
|
lastName = splitName.length > 1 ? splitName[splitName.length-1].toLowerCase() : "";
|
||||||
|
users[i].secondary_text = users[i].full_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstName.lastIndexOf(mentionText,0) === 0
|
if (firstName.lastIndexOf(mentionText,0) === 0
|
||||||
@@ -99,7 +112,7 @@ module.exports = React.createClass({
|
|||||||
<Mention
|
<Mention
|
||||||
ref={'mention' + index}
|
ref={'mention' + index}
|
||||||
username={users[i].username}
|
username={users[i].username}
|
||||||
name={users[i].full_name}
|
secondary_text={users[i].secondary_text}
|
||||||
id={users[i].id}
|
id={users[i].id}
|
||||||
handleClick={this.handleClick} />
|
handleClick={this.handleClick} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ module.exports = React.createClass({
|
|||||||
var mentions = [];
|
var mentions = [];
|
||||||
for (var i = 0; i < matches.length; i++) {
|
for (var i = 0; i < matches.length; i++) {
|
||||||
var m = matches[i].substring(1,matches[i].length).trim();
|
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);
|
mentions.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ function getNotificationsStateFromStores() {
|
|||||||
var mention_key = false;
|
var mention_key = false;
|
||||||
var custom_keys = "";
|
var custom_keys = "";
|
||||||
var first_name_key = false;
|
var first_name_key = false;
|
||||||
|
var all_key = false;
|
||||||
|
var channel_key = false;
|
||||||
|
|
||||||
if (!user.notify_props) {
|
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.mention_keys !== undefined) {
|
if (user.notify_props.mention_keys !== undefined) {
|
||||||
var keys = user.notify_props.mention_keys.split(',');
|
var keys = user.notify_props.mention_keys.split(',');
|
||||||
|
|
||||||
@@ -48,9 +47,17 @@ function getNotificationsStateFromStores() {
|
|||||||
if (user.notify_props.first_name !== undefined) {
|
if (user.notify_props.first_name !== undefined) {
|
||||||
first_name_key = user.notify_props.first_name === "true";
|
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["mention_keys"] = string_keys;
|
||||||
data["first_name"] = this.state.first_name_key ? "true" : "false";
|
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,
|
client.updateUserNotifyProps(data,
|
||||||
function(data) {
|
function(data) {
|
||||||
@@ -120,6 +129,12 @@ var NotificationsTab = React.createClass({
|
|||||||
updateFirstNameKey: function(val) {
|
updateFirstNameKey: function(val) {
|
||||||
this.setState({ first_name_key: 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() {
|
updateCustomMentionKeys: function() {
|
||||||
var checked = this.refs.customcheck.getDOMNode().checked;
|
var checked = this.refs.customcheck.getDOMNode().checked;
|
||||||
|
|
||||||
@@ -339,6 +354,26 @@ var NotificationsTab = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
inputs.push(
|
||||||
|
<div>
|
||||||
|
<div className="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={this.state.all_key} onChange={function(e){self.updateAllKey(e.target.checked);}}>{'Team-wide mentions "@all"'}</input>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
inputs.push(
|
||||||
|
<div>
|
||||||
|
<div className="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={this.state.channel_key} onChange={function(e){self.updateChannelKey(e.target.checked);}}>{'Channel-wide mentions "@channel"'}</input>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
inputs.push(
|
inputs.push(
|
||||||
<div>
|
<div>
|
||||||
<div className="checkbox">
|
<div className="checkbox">
|
||||||
@@ -369,6 +404,8 @@ var NotificationsTab = React.createClass({
|
|||||||
}
|
}
|
||||||
if (this.state.username_key) keys.push(this.props.user.username);
|
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.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(','));
|
if (this.state.custom_keys.length > 0) keys = keys.concat(this.state.custom_keys.split(','));
|
||||||
|
|
||||||
var describe = "";
|
var describe = "";
|
||||||
|
|||||||
@@ -240,6 +240,9 @@ var UserStore = assign({}, EventEmitter.prototype, {
|
|||||||
if (first.length > 0) keys.push(first);
|
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;
|
return keys;
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ module.exports = {
|
|||||||
SERVER_ACTION: null,
|
SERVER_ACTION: null,
|
||||||
VIEW_ACTION: null
|
VIEW_ACTION: null
|
||||||
}),
|
}),
|
||||||
|
SPECIAL_MENTIONS: ['all', 'channel'],
|
||||||
CHARACTER_LIMIT: 4000,
|
CHARACTER_LIMIT: 4000,
|
||||||
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png'],
|
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png'],
|
||||||
AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
|
AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
|
||||||
|
|||||||
@@ -421,10 +421,13 @@ module.exports.textToJsx = function(text, options) {
|
|||||||
highlightSearchClass = " search-highlight";
|
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];
|
var name = explicitMention[1];
|
||||||
// do both a non-case sensitive and case senstive check
|
// 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 suffix = word.match(puncEndRegex);
|
||||||
var prefix = word.match(puncStartRegex);
|
var prefix = word.match(puncStartRegex);
|
||||||
|
|||||||
@@ -56,3 +56,8 @@
|
|||||||
.mention-link {
|
.mention-link {
|
||||||
color:$primary-color;
|
color:$primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mention-align {
|
||||||
|
position:relative;
|
||||||
|
top:5px;
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user