Merging from master
Этот коммит содержится в:
@@ -4,8 +4,14 @@
|
|||||||
|
|
||||||
The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release.
|
The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release.
|
||||||
|
|
||||||
|
- **Release candidate anticipated:** 2015-11-10
|
||||||
- **Final release anticipated:** 2015-11-16
|
- **Final release anticipated:** 2015-11-16
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- IE 10 no longer supported since global share of IE 10 fell below 5%
|
||||||
|
|
||||||
|
|
||||||
## Release v1.1.0
|
## Release v1.1.0
|
||||||
|
|
||||||
Released: 2015-10-16
|
Released: 2015-10-16
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -26,6 +26,12 @@ Please see the [features pages of the Mattermost website](http://www.mattermost.
|
|||||||
- Attach sound, video and image files from mobile devices
|
- Attach sound, video and image files from mobile devices
|
||||||
- Define team-specific branding and color themes across your devices
|
- Define team-specific branding and color themes across your devices
|
||||||
|
|
||||||
|
#### Self-Host Ready
|
||||||
|
|
||||||
|
- Host and manage dozens of teams from a single Mattermost server
|
||||||
|
- Easily manage your Mattermost server using a web-based System Console
|
||||||
|
- Script setup and maintenance using Mattermost command line tools
|
||||||
|
|
||||||
## Learn More
|
## Learn More
|
||||||
|
|
||||||
- [Product Vision and Target Audiences](http://www.mattermost.org/vision/) - What we're solving and for whom are we building
|
- [Product Vision and Target Audiences](http://www.mattermost.org/vision/) - What we're solving and for whom are we building
|
||||||
@@ -38,7 +44,9 @@ Follow us on Twitter at [@MattermostHQ](https://twitter.com/mattermosthq).
|
|||||||
|
|
||||||
## Installing Mattermost
|
## Installing Mattermost
|
||||||
|
|
||||||
There are multiple ways to install Mattermost depending on your needs.
|
Latest stable release of Mattermost is available from http://www.mattermost.org/download/, including binary distribution, and from install guides below.
|
||||||
|
|
||||||
|
If you use Docker, you can [install Mattermost in a single-container preview in one line](https://github.com/mattermost/platform/blob/master/doc/install/Docker-Single-Container.md#one-line-docker-install).
|
||||||
|
|
||||||
#### Quick Start Install for Product Evaluation
|
#### Quick Start Install for Product Evaluation
|
||||||
|
|
||||||
|
|||||||
@@ -568,7 +568,7 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
Srv.Store.Channel().UpdateLastViewedAt(id, c.Session.UserId)
|
Srv.Store.Channel().UpdateLastViewedAt(id, c.Session.UserId)
|
||||||
|
|
||||||
message := model.NewMessage(c.Session.TeamId, id, c.Session.UserId, model.ACTION_VIEWED)
|
message := model.NewMessage(c.Session.TeamId, id, c.Session.UserId, model.ACTION_CHANNEL_VIEWED)
|
||||||
message.Add("channel_id", id)
|
message.Add("channel_id", id)
|
||||||
|
|
||||||
PublishAndForget(message)
|
PublishAndForget(message)
|
||||||
@@ -777,9 +777,8 @@ func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel
|
|||||||
|
|
||||||
UpdateChannelAccessCacheAndForget(channel.TeamId, userIdToRemove, channel.Id)
|
UpdateChannelAccessCacheAndForget(channel.TeamId, userIdToRemove, channel.Id)
|
||||||
|
|
||||||
message := model.NewMessage(channel.TeamId, "", userIdToRemove, model.ACTION_USER_REMOVED)
|
message := model.NewMessage(channel.TeamId, channel.Id, userIdToRemove, model.ACTION_USER_REMOVED)
|
||||||
message.Add("channel_id", channel.Id)
|
message.Add("remover_id", removerUserId)
|
||||||
message.Add("remover", removerUserId)
|
|
||||||
PublishAndForget(message)
|
PublishAndForget(message)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -92,24 +92,9 @@ func (c *WebConn) writePump() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(msg.ChannelId) > 0 {
|
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
||||||
allowed, ok := c.ChannelAccessCache[msg.ChannelId]
|
if err := c.WebSocket.WriteJSON(msg); err != nil {
|
||||||
if !ok {
|
return
|
||||||
allowed = hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, msg.ChannelId, c.UserId))
|
|
||||||
c.ChannelAccessCache[msg.ChannelId] = allowed
|
|
||||||
}
|
|
||||||
|
|
||||||
if allowed {
|
|
||||||
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
|
||||||
if err := c.WebSocket.WriteJSON(msg); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
|
||||||
if err := c.WebSocket.WriteJSON(msg); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
@@ -121,9 +106,11 @@ func (c *WebConn) writePump() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WebConn) updateChannelAccessCache(channelId string) {
|
func (c *WebConn) updateChannelAccessCache(channelId string) bool {
|
||||||
allowed := hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.UserId))
|
allowed := hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.UserId))
|
||||||
c.ChannelAccessCache[channelId] = allowed
|
c.ChannelAccessCache[channelId] = allowed
|
||||||
|
|
||||||
|
return allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPermissionsToChannel(sc store.StoreChannel) bool {
|
func hasPermissionsToChannel(sc store.StoreChannel) bool {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (h *TeamHub) Start() {
|
|||||||
}
|
}
|
||||||
case msg := <-h.broadcast:
|
case msg := <-h.broadcast:
|
||||||
for webCon := range h.connections {
|
for webCon := range h.connections {
|
||||||
if !(webCon.UserId == msg.UserId && msg.Action == model.ACTION_TYPING) {
|
if ShouldSendEvent(webCon, msg) {
|
||||||
select {
|
select {
|
||||||
case webCon.Send <- msg:
|
case webCon.Send <- msg:
|
||||||
default:
|
default:
|
||||||
@@ -86,3 +86,32 @@ func (h *TeamHub) UpdateChannelAccessCache(userId string, channelId string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShouldSendEvent(webCon *WebConn, msg *model.Message) bool {
|
||||||
|
|
||||||
|
if webCon.UserId == msg.UserId {
|
||||||
|
// Don't need to tell the user they are typing
|
||||||
|
if msg.Action == model.ACTION_TYPING {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Don't share a user's view events with other users
|
||||||
|
if msg.Action == model.ACTION_CHANNEL_VIEWED {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only report events to a user who is the subject of the event, or is in the channel of the event
|
||||||
|
if len(msg.ChannelId) > 0 {
|
||||||
|
allowed, ok := webCon.ChannelAccessCache[msg.ChannelId]
|
||||||
|
if !ok {
|
||||||
|
allowed = webCon.updateChannelAccessCache(msg.ChannelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,10 +160,11 @@ exec bin/platform
|
|||||||
proxy_set_header X-Forwarded-Ssl on;
|
proxy_set_header X-Forwarded-Ssl on;
|
||||||
```
|
```
|
||||||
## Finish Mattermost Server setup
|
## Finish Mattermost Server setup
|
||||||
|
|
||||||
1. Navigate to https://mattermost.example.com and create a team and user.
|
1. Navigate to https://mattermost.example.com and create a team and user.
|
||||||
1. The first user in the system is automatically granted the `system_admin` role, which gives you access to the System Console.
|
1. The first user in the system is automatically granted the `system_admin` role, which gives you access to the System Console.
|
||||||
1. From the `town-square` channel click the dropdown and choose the `System Console` option
|
1. From the `town-square` channel click the dropdown and choose the `System Console` option
|
||||||
1. Update Email Settings. We recommend using an email sending service. The example below assumes AmazonSES.
|
1. Update Email Settings. We recommend using an email sending service. The example shows how an Amazon SES setup might look (sample credentials shown below are not real).
|
||||||
* Set *Send Email Notifications* to true
|
* Set *Send Email Notifications* to true
|
||||||
* Set *Require Email Verification* to true
|
* Set *Require Email Verification* to true
|
||||||
* Set *Feedback Name* to `No-Reply`
|
* Set *Feedback Name* to `No-Reply`
|
||||||
|
|||||||
@@ -5,3 +5,8 @@
|
|||||||
1. **DO NOT manipulate the Mattermost database**
|
1. **DO NOT manipulate the Mattermost database**
|
||||||
- In particular, DO NOT delete data from the database, as Mattermost is designed to stop working if data integrity has been compromised. The system is designed to archive content continously and generally assumes data is never deleted.
|
- In particular, DO NOT delete data from the database, as Mattermost is designed to stop working if data integrity has been compromised. The system is designed to archive content continously and generally assumes data is never deleted.
|
||||||
|
|
||||||
|
|
||||||
|
#### Common Issues
|
||||||
|
|
||||||
|
1. Error message in logs when attempting to sign-up: `x509: certificate signed by unknown authority`
|
||||||
|
- This error may appear when attempt to use a self-signed certificate to setup SSL, which is not yet supported by Mattermost. You can resolve this issue by setting up a load balancer like Ngnix. A ticket exists to [add support for self-signed certificates in future](x509: certificate signed by unknown authority).
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ACTION_TYPING = "typing"
|
ACTION_TYPING = "typing"
|
||||||
ACTION_POSTED = "posted"
|
ACTION_POSTED = "posted"
|
||||||
ACTION_POST_EDITED = "post_edited"
|
ACTION_POST_EDITED = "post_edited"
|
||||||
ACTION_POST_DELETED = "post_deleted"
|
ACTION_POST_DELETED = "post_deleted"
|
||||||
ACTION_VIEWED = "viewed"
|
ACTION_CHANNEL_VIEWED = "channel_viewed"
|
||||||
ACTION_NEW_USER = "new_user"
|
ACTION_NEW_USER = "new_user"
|
||||||
ACTION_USER_ADDED = "user_added"
|
ACTION_USER_ADDED = "user_added"
|
||||||
ACTION_USER_REMOVED = "user_removed"
|
ACTION_USER_REMOVED = "user_removed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
|||||||
@@ -6,15 +6,22 @@ package utils
|
|||||||
import (
|
import (
|
||||||
l4g "code.google.com/p/log4go"
|
l4g "code.google.com/p/log4go"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"html"
|
|
||||||
"net"
|
"net"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func encodeRFC2047Word(s string) string {
|
||||||
|
// TODO: use `mime.BEncoding.Encode` instead when `go` >= 1.5
|
||||||
|
// return mime.BEncoding.Encode("utf-8", s)
|
||||||
|
dst := base64.StdEncoding.EncodeToString([]byte(s))
|
||||||
|
return "=?utf-8?b?" + dst + "?="
|
||||||
|
}
|
||||||
|
|
||||||
func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
|
func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var err error
|
var err error
|
||||||
@@ -102,9 +109,10 @@ func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.
|
|||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
headers["From"] = fromMail.String()
|
headers["From"] = fromMail.String()
|
||||||
headers["To"] = toMail.String()
|
headers["To"] = toMail.String()
|
||||||
headers["Subject"] = html.UnescapeString(subject)
|
headers["Subject"] = encodeRFC2047Word(subject)
|
||||||
headers["MIME-version"] = "1.0"
|
headers["MIME-version"] = "1.0"
|
||||||
headers["Content-Type"] = "text/html"
|
headers["Content-Type"] = "text/html; charset=\"utf-8\""
|
||||||
|
headers["Content-Transfer-Encoding"] = "8bit"
|
||||||
headers["Date"] = time.Now().Format(time.RFC1123Z)
|
headers["Date"] = time.Now().Format(time.RFC1123Z)
|
||||||
|
|
||||||
message := ""
|
message := ""
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export default class ActivityLogModal extends React.Component {
|
|||||||
const currentSession = this.state.sessions[i];
|
const currentSession = this.state.sessions[i];
|
||||||
const lastAccessTime = new Date(currentSession.last_activity_at);
|
const lastAccessTime = new Date(currentSession.last_activity_at);
|
||||||
const firstAccessTime = new Date(currentSession.create_at);
|
const firstAccessTime = new Date(currentSession.create_at);
|
||||||
|
let devicePlatform = currentSession.props.platform;
|
||||||
let devicePicture = '';
|
let devicePicture = '';
|
||||||
|
|
||||||
if (currentSession.props.platform === 'Windows') {
|
if (currentSession.props.platform === 'Windows') {
|
||||||
@@ -88,7 +89,12 @@ export default class ActivityLogModal extends React.Component {
|
|||||||
} else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
|
} else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
|
||||||
devicePicture = 'fa fa-apple';
|
devicePicture = 'fa fa-apple';
|
||||||
} else if (currentSession.props.platform === 'Linux') {
|
} else if (currentSession.props.platform === 'Linux') {
|
||||||
devicePicture = 'fa fa-linux';
|
if (currentSession.props.os.indexOf('Android') >= 0) {
|
||||||
|
devicePlatform = 'Android';
|
||||||
|
devicePicture = 'fa fa-android';
|
||||||
|
} else {
|
||||||
|
devicePicture = 'fa fa-linux';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let moreInfo;
|
let moreInfo;
|
||||||
@@ -119,7 +125,7 @@ export default class ActivityLogModal extends React.Component {
|
|||||||
className='activity-log__table'
|
className='activity-log__table'
|
||||||
>
|
>
|
||||||
<div className='activity-log__report'>
|
<div className='activity-log__report'>
|
||||||
<div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
|
<div className='report__platform'><i className={devicePicture} />{devicePlatform}</div>
|
||||||
<div className='report__info'>
|
<div className='report__info'>
|
||||||
<div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
|
<div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
|
||||||
{moreInfo}
|
{moreInfo}
|
||||||
|
|||||||
@@ -40,9 +40,13 @@ export default class AdminController extends React.Component {
|
|||||||
config: AdminStore.getConfig(),
|
config: AdminStore.getConfig(),
|
||||||
teams: AdminStore.getAllTeams(),
|
teams: AdminStore.getAllTeams(),
|
||||||
selectedTeams,
|
selectedTeams,
|
||||||
selected: 'service_settings',
|
selected: props.tab || 'service_settings',
|
||||||
selectedTeam: null
|
selectedTeam: props.teamId || null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!props.tab) {
|
||||||
|
history.replaceState(null, null, `/admin_console/${this.state.selected}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -142,7 +146,9 @@ export default class AdminController extends React.Component {
|
|||||||
} else if (this.state.selected === 'service_settings') {
|
} else if (this.state.selected === 'service_settings') {
|
||||||
tab = <ServiceSettingsTab config={this.state.config} />;
|
tab = <ServiceSettingsTab config={this.state.config} />;
|
||||||
} else if (this.state.selected === 'team_users') {
|
} else if (this.state.selected === 'team_users') {
|
||||||
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
|
if (this.state.teams) {
|
||||||
|
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export default class AdminSidebar extends React.Component {
|
|||||||
handleClick(name, teamId, e) {
|
handleClick(name, teamId, e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.selectTab(name, teamId);
|
this.props.selectTab(name, teamId);
|
||||||
|
history.pushState({name: name, teamId: teamId}, null, `/admin_console/${name}/${teamId || ''}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelected(name, teamId) {
|
isSelected(name, teamId) {
|
||||||
@@ -53,6 +54,9 @@ export default class AdminSidebar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
if ($(window).width() > 768) {
|
||||||
|
$('.nav-pills__container').perfectScrollbar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showTeamSelect(e) {
|
showTeamSelect(e) {
|
||||||
|
|||||||
@@ -440,9 +440,11 @@ export default class EmailSettings extends React.Component {
|
|||||||
className='table table-bordered'
|
className='table table-bordered'
|
||||||
cellPadding='5'
|
cellPadding='5'
|
||||||
>
|
>
|
||||||
<tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
|
<tbody>
|
||||||
<tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
|
<tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
|
||||||
<tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
|
<tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
|
||||||
|
<tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div className='help-text'>
|
<div className='help-text'>
|
||||||
|
|||||||
@@ -249,22 +249,24 @@ export default class LogSettings extends React.Component {
|
|||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={!this.state.fileEnable}
|
disabled={!this.state.fileEnable}
|
||||||
/>
|
/>
|
||||||
<p className='help-text'>
|
<div className='help-text'>
|
||||||
{'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'}
|
{'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'}
|
||||||
<div className='help-text'>
|
<div className='help-text'>
|
||||||
<table
|
<table
|
||||||
className='table table-bordered'
|
className='table table-bordered'
|
||||||
cellPadding='5'
|
cellPadding='5'
|
||||||
>
|
>
|
||||||
<tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
|
<tbody>
|
||||||
<tr><td className='help-text'>{'%D'}</td><td className='help-text'>{'Date (2006/01/02)'}</td></tr>
|
<tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
|
||||||
<tr><td className='help-text'>{'%d'}</td><td className='help-text'>{'Date (01/02/06)'}</td></tr>
|
<tr><td className='help-text'>{'%D'}</td><td className='help-text'>{'Date (2006/01/02)'}</td></tr>
|
||||||
<tr><td className='help-text'>{'%L'}</td><td className='help-text'>{'Level (DEBG, INFO, EROR)'}</td></tr>
|
<tr><td className='help-text'>{'%d'}</td><td className='help-text'>{'Date (01/02/06)'}</td></tr>
|
||||||
<tr><td className='help-text'>{'%S'}</td><td className='help-text'>{'Source'}</td></tr>
|
<tr><td className='help-text'>{'%L'}</td><td className='help-text'>{'Level (DEBG, INFO, EROR)'}</td></tr>
|
||||||
<tr><td className='help-text'>{'%M'}</td><td className='help-text'>{'Message'}</td></tr>
|
<tr><td className='help-text'>{'%S'}</td><td className='help-text'>{'Source'}</td></tr>
|
||||||
|
<tr><td className='help-text'>{'%M'}</td><td className='help-text'>{'Message'}</td></tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
const ChannelStore = require('../stores/channel_store.jsx');
|
const ChannelStore = require('../stores/channel_store.jsx');
|
||||||
const UserStore = require('../stores/user_store.jsx');
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
const PostStore = require('../stores/post_store.jsx');
|
const PostStore = require('../stores/post_store.jsx');
|
||||||
const SocketStore = require('../stores/socket_store.jsx');
|
|
||||||
const NavbarSearchBox = require('./search_bar.jsx');
|
const NavbarSearchBox = require('./search_bar.jsx');
|
||||||
const AsyncClient = require('../utils/async_client.jsx');
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
const Client = require('../utils/client.jsx');
|
const Client = require('../utils/client.jsx');
|
||||||
@@ -25,7 +24,6 @@ export default class ChannelHeader extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onListenerChange = this.onListenerChange.bind(this);
|
this.onListenerChange = this.onListenerChange.bind(this);
|
||||||
this.onSocketChange = this.onSocketChange.bind(this);
|
|
||||||
this.handleLeave = this.handleLeave.bind(this);
|
this.handleLeave = this.handleLeave.bind(this);
|
||||||
this.searchMentions = this.searchMentions.bind(this);
|
this.searchMentions = this.searchMentions.bind(this);
|
||||||
|
|
||||||
@@ -45,7 +43,6 @@ export default class ChannelHeader extends React.Component {
|
|||||||
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
|
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
|
||||||
PostStore.addSearchChangeListener(this.onListenerChange);
|
PostStore.addSearchChangeListener(this.onListenerChange);
|
||||||
UserStore.addChangeListener(this.onListenerChange);
|
UserStore.addChangeListener(this.onListenerChange);
|
||||||
SocketStore.addChangeListener(this.onSocketChange);
|
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
ChannelStore.removeChangeListener(this.onListenerChange);
|
||||||
@@ -60,16 +57,9 @@ export default class ChannelHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
|
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
|
||||||
}
|
}
|
||||||
onSocketChange(msg) {
|
|
||||||
if (msg.action === 'new_user' ||
|
|
||||||
msg.action === 'user_added' ||
|
|
||||||
(msg.action === 'user_removed' && msg.user_id !== UserStore.getCurrentId())) {
|
|
||||||
AsyncClient.getChannelExtraInfo(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleLeave() {
|
handleLeave() {
|
||||||
Client.leaveChannel(this.state.channel.id,
|
Client.leaveChannel(this.state.channel.id,
|
||||||
function handleLeaveSuccess() {
|
() => {
|
||||||
AppDispatcher.handleViewAction({
|
AppDispatcher.handleViewAction({
|
||||||
type: ActionTypes.LEAVE_CHANNEL,
|
type: ActionTypes.LEAVE_CHANNEL,
|
||||||
id: this.state.channel.id
|
id: this.state.channel.id
|
||||||
@@ -77,8 +67,8 @@ export default class ChannelHeader extends React.Component {
|
|||||||
|
|
||||||
const townsquare = ChannelStore.getByName('town-square');
|
const townsquare = ChannelStore.getByName('town-square');
|
||||||
Utils.switchChannel(townsquare);
|
Utils.switchChannel(townsquare);
|
||||||
}.bind(this),
|
},
|
||||||
function handleLeaveError(err) {
|
(err) => {
|
||||||
AsyncClient.dispatchError(err, 'handleLeave');
|
AsyncClient.dispatchError(err, 'handleLeave');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default class EditPostModal extends React.Component {
|
|||||||
refocusId: options.refocusId || ''
|
refocusId: options.refocusId || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
$(React.findDOMNode(this.refs.modal)).modal('show');
|
$(ReactDOM.findDOMNode(this.refs.modal)).modal('show');
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -92,7 +92,7 @@ export default class EditPostModal extends React.Component {
|
|||||||
$('#edit_textbox').get(0).focus();
|
$('#edit_textbox').get(0).focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
|
$(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
|
||||||
if (self.state.refocusId !== '') {
|
if (self.state.refocusId !== '') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$(self.state.refocusId).get(0).focus();
|
$(self.state.refocusId).get(0).focus();
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default class MoreChannels extends React.Component {
|
|||||||
moreChannels = <LoadingScreen />;
|
moreChannels = <LoadingScreen />;
|
||||||
} else if (channels.length) {
|
} else if (channels.length) {
|
||||||
moreChannels = (
|
moreChannels = (
|
||||||
<table className='more-channel-table table'>
|
<table className='more-table table'>
|
||||||
<tbody>
|
<tbody>
|
||||||
{channels.map(function cMap(channel, index) {
|
{channels.map(function cMap(channel, index) {
|
||||||
var joinButton;
|
var joinButton;
|
||||||
@@ -108,8 +108,8 @@ export default class MoreChannels extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<tr key={channel.id}>
|
<tr key={channel.id}>
|
||||||
<td>
|
<td>
|
||||||
<p className='more-channel-name'>{channel.display_name}</p>
|
<p className='more-name'>{channel.display_name}</p>
|
||||||
<p className='more-channel-description'>{channel.description}</p>
|
<p className='more-description'>{channel.description}</p>
|
||||||
</td>
|
</td>
|
||||||
<td className='td--action'>
|
<td className='td--action'>
|
||||||
{joinButton}
|
{joinButton}
|
||||||
|
|||||||
@@ -140,12 +140,12 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
if (user.nickname) {
|
if (user.nickname) {
|
||||||
const separator = fullName ? ' - ' : '';
|
const separator = fullName ? ' - ' : '';
|
||||||
details.push(
|
details.push(
|
||||||
<span
|
<p
|
||||||
key={`${user.nickname}__nickname`}
|
key={`${user.nickname}__nickname`}
|
||||||
className='nickname'
|
className='more-description'
|
||||||
>
|
>
|
||||||
{separator + user.nickname}
|
{separator + user.nickname}
|
||||||
</span>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,31 +170,38 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<tr>
|
||||||
key={user.id}
|
<td
|
||||||
className='direct-channel'
|
key={user.id}
|
||||||
>
|
className='direct-channel'
|
||||||
<div className='col-xs-1 image-div'>
|
>
|
||||||
<img
|
<img
|
||||||
className='profile-image'
|
className='profile-img pull-left'
|
||||||
|
width='38'
|
||||||
|
height='38'
|
||||||
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
|
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
<div className='more-name'>
|
||||||
<div className='col-xs-9'>
|
|
||||||
<div className='username'>
|
|
||||||
{user.username}
|
{user.username}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{details}
|
||||||
{details}
|
</td>
|
||||||
</div>
|
<td className='td--action lg'>
|
||||||
</div>
|
|
||||||
<div className='col-xs-2 btn-div'>
|
|
||||||
{joinButton}
|
{joinButton}
|
||||||
</div>
|
</td>
|
||||||
</li>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!prevProps.show && this.props.show) {
|
||||||
|
$(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 300);
|
||||||
|
if ($(window).width() > 768) {
|
||||||
|
$(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.props.show) {
|
if (!this.props.show) {
|
||||||
return null;
|
return null;
|
||||||
@@ -213,7 +220,7 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
const userEntries = users.map(this.createRowForUser);
|
const userEntries = users.map(this.createRowForUser);
|
||||||
|
|
||||||
if (userEntries.length === 0) {
|
if (userEntries.length === 0) {
|
||||||
userEntries.push(<li key='no-users-found'>{'No users found :('}</li>);
|
userEntries.push(<tr key='no-users-found'><td>{'No users found :('}</td></tr>);
|
||||||
}
|
}
|
||||||
|
|
||||||
let memberString = 'Member';
|
let memberString = 'Member';
|
||||||
@@ -232,26 +239,35 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
<Modal
|
<Modal
|
||||||
className='modal-direct-channels'
|
className='modal-direct-channels'
|
||||||
show={this.props.show}
|
show={this.props.show}
|
||||||
bsSize='large'
|
|
||||||
onHide={this.handleHide}
|
onHide={this.handleHide}
|
||||||
>
|
>
|
||||||
<Modal.Header closeButton={true}>
|
<Modal.Header closeButton={true}>
|
||||||
<Modal.Title>{'More Direct Messages'}</Modal.Title>
|
<Modal.Title>{'Team Directory'}</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<div>
|
<div className='row filter-row'>
|
||||||
<input
|
<div className='col-sm-6'>
|
||||||
ref='filter'
|
<input
|
||||||
className='form-control filter-textbox'
|
ref='filter'
|
||||||
placeholder='Search members'
|
className='form-control filter-textbox'
|
||||||
onInput={this.handleFilterChange}
|
placeholder='Search members'
|
||||||
style={{width: '200px', display: 'inline'}}
|
onInput={this.handleFilterChange}
|
||||||
/>
|
/>
|
||||||
<span className='member-count pull-right'>{count}</span>
|
</div>
|
||||||
|
<div className='col-sm-6'>
|
||||||
|
<span className='member-count'>{count}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref='userList'
|
||||||
|
className='user-list'
|
||||||
|
>
|
||||||
|
<table className='more-table table'>
|
||||||
|
<tbody>
|
||||||
|
{userEntries}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<ul className='user-list'>
|
|
||||||
{userEntries}
|
|
||||||
</ul>
|
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var SocketStore = require('../stores/socket_store.jsx');
|
const SocketStore = require('../stores/socket_store.jsx');
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
|
|
||||||
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const SocketEvents = Constants.SocketEvents;
|
||||||
|
|
||||||
export default class MsgTyping extends React.Component {
|
export default class MsgTyping extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -33,9 +36,9 @@ export default class MsgTyping extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onChange(msg) {
|
onChange(msg) {
|
||||||
if (msg.action === 'typing' &&
|
if (msg.action === SocketEvents.TYPING &&
|
||||||
this.props.channelId === msg.channel_id &&
|
this.props.channelId === msg.channel_id &&
|
||||||
this.props.parentId === msg.props.parent_id) {
|
this.props.parentId === msg.props.parent_id) {
|
||||||
this.lastTime = new Date().getTime();
|
this.lastTime = new Date().getTime();
|
||||||
|
|
||||||
var username = 'Someone';
|
var username = 'Someone';
|
||||||
@@ -52,7 +55,7 @@ export default class MsgTyping extends React.Component {
|
|||||||
}
|
}
|
||||||
}.bind(this), 3000);
|
}.bind(this), 3000);
|
||||||
}
|
}
|
||||||
} else if (msg.action === 'posted' && msg.channel_id === this.props.channelId) {
|
} else if (msg.action === SocketEvents.POSTED && msg.channel_id === this.props.channelId) {
|
||||||
this.setState({text: ''});
|
this.setState({text: ''});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,20 @@ export default class PopoverListMembers extends React.Component {
|
|||||||
const teamMembers = UserStore.getProfilesUsernameMap();
|
const teamMembers = UserStore.getProfilesUsernameMap();
|
||||||
|
|
||||||
if (members && teamMembers) {
|
if (members && teamMembers) {
|
||||||
members.sort(function compareByLocal(a, b) {
|
members.sort((a, b) => {
|
||||||
return a.username.localeCompare(b.username);
|
return a.username.localeCompare(b.username);
|
||||||
});
|
});
|
||||||
|
|
||||||
members.forEach(function addMemberElement(m) {
|
members.forEach((m, i) => {
|
||||||
if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
|
if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
|
||||||
popoverHtml.push(<div className='text--nowrap'>{m.username}</div>);
|
popoverHtml.push(
|
||||||
|
<div
|
||||||
|
className='text--nowrap'
|
||||||
|
key={'popover-member-' + i}
|
||||||
|
>
|
||||||
|
{m.username}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -57,8 +64,15 @@ export default class PopoverListMembers extends React.Component {
|
|||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
trigger='click'
|
trigger='click'
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
rootClose='true'
|
rootClose={true}
|
||||||
overlay={<Popover title='Members'>{popoverHtml}</Popover>}
|
overlay={
|
||||||
|
<Popover
|
||||||
|
title='Members'
|
||||||
|
id='member-list-popover'
|
||||||
|
>
|
||||||
|
{popoverHtml}
|
||||||
|
</Popover>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div id='member_popover'>
|
<div id='member_popover'>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default class PostBody extends React.Component {
|
|||||||
}
|
}
|
||||||
var metadata = data.items[0].snippet;
|
var metadata = data.items[0].snippet;
|
||||||
this.receivedYoutubeData = true;
|
this.receivedYoutubeData = true;
|
||||||
this.setState({youtubeUploader: metadata.channelTitle, youtubeTitle: metadata.title});
|
this.setState({youtubeTitle: metadata.title});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.mm_config.GoogleDeveloperKey && !this.receivedYoutubeData) {
|
if (global.window.mm_config.GoogleDeveloperKey && !this.receivedYoutubeData) {
|
||||||
@@ -134,18 +134,12 @@ export default class PostBody extends React.Component {
|
|||||||
header = header + ' - ';
|
header = header + ' - ';
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploader = this.state.youtubeUploader;
|
|
||||||
if (!uploader) {
|
|
||||||
uploader = 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='post-comment'>
|
<div className='post-comment'>
|
||||||
<h4>
|
<h4>
|
||||||
<span className='video-type'>{header}</span>
|
<span className='video-type'>{header}</span>
|
||||||
<span className='video-title'><a href={link}>{this.state.youtubeTitle}</a></span>
|
<span className='video-title'><a href={link}>{this.state.youtubeTitle}</a></span>
|
||||||
</h4>
|
</h4>
|
||||||
<h4 className='video-uploader'>{uploader}</h4>
|
|
||||||
<div
|
<div
|
||||||
className='video-div embed-responsive-item'
|
className='video-div embed-responsive-item'
|
||||||
id={youtubeId}
|
id={youtubeId}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export default class PostInfo extends React.Component {
|
|||||||
<ul className='post-header post-info'>
|
<ul className='post-header post-info'>
|
||||||
<li className='post-header-col'>
|
<li className='post-header-col'>
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
delayShow='500'
|
delayShow={500}
|
||||||
container={this}
|
container={this}
|
||||||
placement='top'
|
placement='top'
|
||||||
overlay={tooltip}
|
overlay={tooltip}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var PostStore = require('../stores/post_store.jsx');
|
const Post = require('./post.jsx');
|
||||||
var ChannelStore = require('../stores/channel_store.jsx');
|
const UserProfile = require('./user_profile.jsx');
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
var PreferenceStore = require('../stores/preference_store.jsx');
|
const LoadingScreen = require('./loading_screen.jsx');
|
||||||
var UserProfile = require('./user_profile.jsx');
|
|
||||||
var AsyncClient = require('../utils/async_client.jsx');
|
const PostStore = require('../stores/post_store.jsx');
|
||||||
var Post = require('./post.jsx');
|
const ChannelStore = require('../stores/channel_store.jsx');
|
||||||
var LoadingScreen = require('./loading_screen.jsx');
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
var SocketStore = require('../stores/socket_store.jsx');
|
const SocketStore = require('../stores/socket_store.jsx');
|
||||||
var utils = require('../utils/utils.jsx');
|
const PreferenceStore = require('../stores/preference_store.jsx');
|
||||||
var Client = require('../utils/client.jsx');
|
|
||||||
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
const utils = require('../utils/utils.jsx');
|
||||||
var Constants = require('../utils/constants.jsx');
|
const Client = require('../utils/client.jsx');
|
||||||
var ActionTypes = Constants.ActionTypes;
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
const SocketEvents = Constants.SocketEvents;
|
||||||
|
|
||||||
|
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
|
|
||||||
export default class PostList extends React.Component {
|
export default class PostList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -58,7 +62,7 @@ export default class PostList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
postList.order.sort(function postSort(a, b) {
|
postList.order.sort((a, b) => {
|
||||||
if (postList.posts[a].create_at > postList.posts[b].create_at) {
|
if (postList.posts[a].create_at > postList.posts[b].create_at) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -82,7 +86,7 @@ export default class PostList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
postList: postList
|
postList
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -263,14 +267,14 @@ export default class PostList extends React.Component {
|
|||||||
Client.getPosts(
|
Client.getPosts(
|
||||||
id,
|
id,
|
||||||
PostStore.getLatestUpdate(id),
|
PostStore.getLatestUpdate(id),
|
||||||
function success() {
|
() => {
|
||||||
this.loadInProgress = false;
|
this.loadInProgress = false;
|
||||||
this.setState({isFirstLoadComplete: true});
|
this.setState({isFirstLoadComplete: true});
|
||||||
}.bind(this),
|
},
|
||||||
function fail() {
|
() => {
|
||||||
this.loadInProgress = false;
|
this.loadInProgress = false;
|
||||||
this.setState({isFirstLoadComplete: true});
|
this.setState({isFirstLoadComplete: true});
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
onChange() {
|
onChange() {
|
||||||
@@ -281,28 +285,16 @@ export default class PostList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onSocketChange(msg) {
|
onSocketChange(msg) {
|
||||||
var post;
|
if (msg.action === SocketEvents.POST_DELETED) {
|
||||||
if (msg.action === 'posted' || msg.action === 'post_edited') {
|
|
||||||
post = JSON.parse(msg.props.post);
|
|
||||||
PostStore.storePost(post);
|
|
||||||
} else if (msg.action === 'post_deleted') {
|
|
||||||
var activeRoot = $(document.activeElement).closest('.comment-create-body')[0];
|
var activeRoot = $(document.activeElement).closest('.comment-create-body')[0];
|
||||||
var activeRootPostId = '';
|
var activeRootPostId = '';
|
||||||
if (activeRoot && activeRoot.id.length > 0) {
|
if (activeRoot && activeRoot.id.length > 0) {
|
||||||
activeRootPostId = activeRoot.id;
|
activeRootPostId = activeRoot.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
post = JSON.parse(msg.props.post);
|
|
||||||
|
|
||||||
PostStore.storeUnseenDeletedPost(post);
|
|
||||||
PostStore.removePost(post, true);
|
|
||||||
PostStore.emitChange();
|
|
||||||
|
|
||||||
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
|
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
|
||||||
$('#post_deleted').modal('show');
|
$('#post_deleted').modal('show');
|
||||||
}
|
}
|
||||||
} else if (msg.action === 'new_user') {
|
|
||||||
AsyncClient.getProfiles();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onTimeChange() {
|
onTimeChange() {
|
||||||
@@ -352,7 +344,7 @@ export default class PostList extends React.Component {
|
|||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
<i className='fa fa-pencil'></i>Set a description
|
<i className='fa fa-pencil'></i>{'Set a description'}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default class RhsComment extends React.Component {
|
|||||||
|
|
||||||
var post = this.props.post;
|
var post = this.props.post;
|
||||||
Client.createPost(post, post.channel_id,
|
Client.createPost(post, post.channel_id,
|
||||||
function success(data) {
|
(data) => {
|
||||||
AsyncClient.getPosts(post.channel_id);
|
AsyncClient.getPosts(post.channel_id);
|
||||||
|
|
||||||
var channel = ChannelStore.get(post.channel_id);
|
var channel = ChannelStore.get(post.channel_id);
|
||||||
@@ -43,11 +43,11 @@ export default class RhsComment extends React.Component {
|
|||||||
post: data
|
post: data
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function fail() {
|
() => {
|
||||||
post.state = Constants.POST_FAILED;
|
post.state = Constants.POST_FAILED;
|
||||||
PostStore.updatePendingPost(post);
|
PostStore.updatePendingPost(post);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
post.state = Constants.POST_LOADING;
|
post.state = Constants.POST_LOADING;
|
||||||
@@ -84,7 +84,10 @@ export default class RhsComment extends React.Component {
|
|||||||
|
|
||||||
if (isOwner) {
|
if (isOwner) {
|
||||||
dropdownContents.push(
|
dropdownContents.push(
|
||||||
<li role='presentation'>
|
<li
|
||||||
|
role='presentation'
|
||||||
|
key='edit-button'
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
role='menuitem'
|
role='menuitem'
|
||||||
@@ -95,7 +98,7 @@ export default class RhsComment extends React.Component {
|
|||||||
data-postid={post.id}
|
data-postid={post.id}
|
||||||
data-channelid={post.channel_id}
|
data-channelid={post.channel_id}
|
||||||
>
|
>
|
||||||
Edit
|
{'Edit'}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
@@ -103,7 +106,10 @@ export default class RhsComment extends React.Component {
|
|||||||
|
|
||||||
if (isOwner || isAdmin) {
|
if (isOwner || isAdmin) {
|
||||||
dropdownContents.push(
|
dropdownContents.push(
|
||||||
<li role='presentation'>
|
<li
|
||||||
|
role='presentation'
|
||||||
|
key='delete-button'
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
role='menuitem'
|
role='menuitem'
|
||||||
@@ -114,7 +120,7 @@ export default class RhsComment extends React.Component {
|
|||||||
data-channelid={post.channel_id}
|
data-channelid={post.channel_id}
|
||||||
data-comments={0}
|
data-comments={0}
|
||||||
>
|
>
|
||||||
Delete
|
{'Delete'}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
@@ -162,7 +168,7 @@ export default class RhsComment extends React.Component {
|
|||||||
href='#'
|
href='#'
|
||||||
onClick={this.retryComment}
|
onClick={this.retryComment}
|
||||||
>
|
>
|
||||||
Retry
|
{'Retry'}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else if (post.state === Constants.POST_LOADING) {
|
} else if (post.state === Constants.POST_LOADING) {
|
||||||
@@ -213,14 +219,14 @@ export default class RhsComment extends React.Component {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='post-body'>
|
<div className='post-body'>
|
||||||
<p className={postClass}>
|
<div className={postClass}>
|
||||||
{loading}
|
{loading}
|
||||||
<div
|
<div
|
||||||
ref='message_holder'
|
ref='message_holder'
|
||||||
onClick={TextFormatting.handleClick}
|
onClick={TextFormatting.handleClick}
|
||||||
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(post.message)}}
|
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(post.message)}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</div>
|
||||||
{fileAttachment}
|
{fileAttachment}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
const AsyncClient = require('../utils/async_client.jsx');
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
const BrowserStore = require('../stores/browser_store.jsx');
|
|
||||||
const ChannelStore = require('../stores/channel_store.jsx');
|
const ChannelStore = require('../stores/channel_store.jsx');
|
||||||
const Client = require('../utils/client.jsx');
|
const Client = require('../utils/client.jsx');
|
||||||
const Constants = require('../utils/constants.jsx');
|
const Constants = require('../utils/constants.jsx');
|
||||||
@@ -11,7 +10,6 @@ const NewChannelFlow = require('./new_channel_flow.jsx');
|
|||||||
const MoreDirectChannels = require('./more_direct_channels.jsx');
|
const MoreDirectChannels = require('./more_direct_channels.jsx');
|
||||||
const SearchBox = require('./search_bar.jsx');
|
const SearchBox = require('./search_bar.jsx');
|
||||||
const SidebarHeader = require('./sidebar_header.jsx');
|
const SidebarHeader = require('./sidebar_header.jsx');
|
||||||
const SocketStore = require('../stores/socket_store.jsx');
|
|
||||||
const TeamStore = require('../stores/team_store.jsx');
|
const TeamStore = require('../stores/team_store.jsx');
|
||||||
const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
|
const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
|
||||||
const UserStore = require('../stores/user_store.jsx');
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
@@ -46,7 +44,7 @@ export default class Sidebar extends React.Component {
|
|||||||
|
|
||||||
const state = this.getStateFromStores();
|
const state = this.getStateFromStores();
|
||||||
state.newChannelModalType = '';
|
state.newChannelModalType = '';
|
||||||
state.showMoreDirectChannelsModal = false;
|
state.showDirectChannelsModal = false;
|
||||||
state.loadingDMChannel = -1;
|
state.loadingDMChannel = -1;
|
||||||
|
|
||||||
this.state = state;
|
this.state = state;
|
||||||
@@ -129,10 +127,11 @@ export default class Sidebar extends React.Component {
|
|||||||
UserStore.addChangeListener(this.onChange);
|
UserStore.addChangeListener(this.onChange);
|
||||||
UserStore.addStatusesChangeListener(this.onChange);
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
TeamStore.addChangeListener(this.onChange);
|
TeamStore.addChangeListener(this.onChange);
|
||||||
SocketStore.addChangeListener(this.onSocketChange);
|
|
||||||
PreferenceStore.addChangeListener(this.onChange);
|
PreferenceStore.addChangeListener(this.onChange);
|
||||||
|
|
||||||
$('.nav-pills__container').perfectScrollbar();
|
if ($(window).width() > 768) {
|
||||||
|
$('.nav-pills__container').perfectScrollbar();
|
||||||
|
}
|
||||||
|
|
||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
this.updateUnreadIndicators();
|
this.updateUnreadIndicators();
|
||||||
@@ -160,7 +159,6 @@ export default class Sidebar extends React.Component {
|
|||||||
UserStore.removeChangeListener(this.onChange);
|
UserStore.removeChangeListener(this.onChange);
|
||||||
UserStore.removeStatusesChangeListener(this.onChange);
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
TeamStore.removeChangeListener(this.onChange);
|
TeamStore.removeChangeListener(this.onChange);
|
||||||
SocketStore.removeChangeListener(this.onSocketChange);
|
|
||||||
PreferenceStore.removeChangeListener(this.onChange);
|
PreferenceStore.removeChangeListener(this.onChange);
|
||||||
}
|
}
|
||||||
onChange() {
|
onChange() {
|
||||||
@@ -169,94 +167,6 @@ export default class Sidebar extends React.Component {
|
|||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onSocketChange(msg) {
|
|
||||||
if (msg.action === 'posted') {
|
|
||||||
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
|
||||||
if (window.isActive) {
|
|
||||||
AsyncClient.updateLastViewedAt();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AsyncClient.getChannels();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UserStore.getCurrentId() !== msg.user_id) {
|
|
||||||
var mentions = [];
|
|
||||||
if (msg.props.mentions) {
|
|
||||||
mentions = JSON.parse(msg.props.mentions);
|
|
||||||
}
|
|
||||||
var channel = ChannelStore.get(msg.channel_id);
|
|
||||||
|
|
||||||
const user = UserStore.getCurrentUser();
|
|
||||||
const member = ChannelStore.getMember(msg.channel_id);
|
|
||||||
|
|
||||||
var notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
|
|
||||||
if (notifyLevel === 'default') {
|
|
||||||
notifyLevel = user.notify_props.desktop;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifyLevel === 'none') {
|
|
||||||
return;
|
|
||||||
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var username = 'Someone';
|
|
||||||
if (UserStore.hasProfile(msg.user_id)) {
|
|
||||||
username = UserStore.getProfile(msg.user_id).username;
|
|
||||||
}
|
|
||||||
|
|
||||||
var title = 'Posted';
|
|
||||||
if (channel) {
|
|
||||||
title = channel.display_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var repRegex = new RegExp('<br>', 'g');
|
|
||||||
var post = JSON.parse(msg.props.post);
|
|
||||||
var msgProps = msg.props;
|
|
||||||
var notifyText = post.message.replace(repRegex, '\n').replace(/\n+/g, ' ').replace('<mention>', '').replace('</mention>', '');
|
|
||||||
|
|
||||||
if (notifyText.length > 50) {
|
|
||||||
notifyText = notifyText.substring(0, 49) + '...';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifyText.length === 0) {
|
|
||||||
if (msgProps.image) {
|
|
||||||
Utils.notifyMe(title, username + ' uploaded an image', channel);
|
|
||||||
} else if (msgProps.otherFile) {
|
|
||||||
Utils.notifyMe(title, username + ' uploaded a file', channel);
|
|
||||||
} else {
|
|
||||||
Utils.notifyMe(title, username + ' did something new', channel);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
|
|
||||||
}
|
|
||||||
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
|
|
||||||
Utils.ding();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (msg.action === 'viewed') {
|
|
||||||
if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
|
|
||||||
AsyncClient.getChannel(msg.channel_id);
|
|
||||||
}
|
|
||||||
} else if (msg.action === 'user_added') {
|
|
||||||
if (UserStore.getCurrentId() === msg.user_id) {
|
|
||||||
AsyncClient.getChannel(msg.channel_id);
|
|
||||||
}
|
|
||||||
} else if (msg.action === 'user_removed') {
|
|
||||||
if (msg.user_id === UserStore.getCurrentId()) {
|
|
||||||
AsyncClient.getChannels(true);
|
|
||||||
|
|
||||||
if (msg.props.remover !== msg.user_id && msg.props.channel_id === ChannelStore.getCurrentId() && $('#removed_from_channel').length > 0) {
|
|
||||||
var sentState = {};
|
|
||||||
sentState.channelName = ChannelStore.getCurrent().display_name;
|
|
||||||
sentState.remover = UserStore.getProfile(msg.props.remover).username;
|
|
||||||
|
|
||||||
BrowserStore.setItem('channel-removed-state', sentState);
|
|
||||||
$('#removed_from_channel').modal('show');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateTitle() {
|
updateTitle() {
|
||||||
const channel = ChannelStore.getCurrent();
|
const channel = ChannelStore.getCurrent();
|
||||||
if (channel) {
|
if (channel) {
|
||||||
@@ -471,11 +381,13 @@ export default class Sidebar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let closeButton = null;
|
let closeButton = null;
|
||||||
const removeTooltip = <Tooltip>{'Remove from list'}</Tooltip>;
|
const removeTooltip = (
|
||||||
|
<Tooltip id='remove-dm-tooltip'>{'Remove from list'}</Tooltip>
|
||||||
|
);
|
||||||
if (handleClose && !badge) {
|
if (handleClose && !badge) {
|
||||||
closeButton = (
|
closeButton = (
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
delayShow='1000'
|
delayShow={1000}
|
||||||
placement='top'
|
placement='top'
|
||||||
overlay={removeTooltip}
|
overlay={removeTooltip}
|
||||||
>
|
>
|
||||||
@@ -564,8 +476,12 @@ export default class Sidebar extends React.Component {
|
|||||||
showChannelModal = true;
|
showChannelModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createChannelTootlip = <Tooltip>{'Create new channel'}</Tooltip>;
|
const createChannelTootlip = (
|
||||||
const createGroupTootlip = <Tooltip>{'Create new group'}</Tooltip>;
|
<Tooltip id='new-channel-tooltip' >{'Create new channel'}</Tooltip>
|
||||||
|
);
|
||||||
|
const createGroupTootlip = (
|
||||||
|
<Tooltip id='new-group-tooltip'>{'Create new group'}</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -607,7 +523,7 @@ export default class Sidebar extends React.Component {
|
|||||||
<h4>
|
<h4>
|
||||||
{'Channels'}
|
{'Channels'}
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
delayShow='500'
|
delayShow={500}
|
||||||
placement='top'
|
placement='top'
|
||||||
overlay={createChannelTootlip}
|
overlay={createChannelTootlip}
|
||||||
>
|
>
|
||||||
@@ -640,7 +556,7 @@ export default class Sidebar extends React.Component {
|
|||||||
<h4>
|
<h4>
|
||||||
{'Private Groups'}
|
{'Private Groups'}
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
delayShow='500'
|
delayShow={500}
|
||||||
placement='top'
|
placement='top'
|
||||||
overlay={createGroupTootlip}
|
overlay={createGroupTootlip}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ export default class TeamSignupUsernamePage extends React.Component {
|
|||||||
}
|
}
|
||||||
submitBack(e) {
|
submitBack(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.state.wizard = 'send_invites';
|
if (global.window.config.SendEmailNotifications === 'true') {
|
||||||
|
this.props.state.wizard = 'send_invites';
|
||||||
|
} else {
|
||||||
|
this.props.state.wizard = 'team_url';
|
||||||
|
}
|
||||||
|
|
||||||
this.props.updateParent(this.props.state);
|
this.props.updateParent(this.props.state);
|
||||||
}
|
}
|
||||||
submitNext(e) {
|
submitNext(e) {
|
||||||
|
|||||||
@@ -65,22 +65,33 @@ export default class UserProfile extends React.Component {
|
|||||||
|
|
||||||
var dataContent = [];
|
var dataContent = [];
|
||||||
dataContent.push(
|
dataContent.push(
|
||||||
<img className='user-popover__image'
|
<img
|
||||||
|
className='user-popover__image'
|
||||||
src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at}
|
src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at}
|
||||||
height='128'
|
height='128'
|
||||||
width='128'
|
width='128'
|
||||||
|
key='user-popover-image'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!global.window.mm_config.ShowEmailAddress === 'true') {
|
if (!global.window.mm_config.ShowEmailAddress === 'true') {
|
||||||
dataContent.push(<div className='text-nowrap'>{'Email not shared'}</div>);
|
dataContent.push(
|
||||||
|
<div
|
||||||
|
className='text-nowrap'
|
||||||
|
key='user-popover-no-email'
|
||||||
|
>
|
||||||
|
{'Email not shared'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
dataContent.push(
|
dataContent.push(
|
||||||
<div
|
<div
|
||||||
data-toggle='tooltip'
|
data-toggle='tooltip'
|
||||||
title="' + this.state.profile.email + '"
|
title={this.state.profile.email}
|
||||||
|
key='user-popover-email'
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="mailto:' + this.state.profile.email + '"
|
href={'mailto:' + this.state.profile.email}
|
||||||
className='text-nowrap text-lowercase user-popover__email'
|
className='text-nowrap text-lowercase user-popover__email'
|
||||||
>
|
>
|
||||||
{this.state.profile.email}
|
{this.state.profile.email}
|
||||||
@@ -93,15 +104,22 @@ export default class UserProfile extends React.Component {
|
|||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
trigger='click'
|
trigger='click'
|
||||||
placement='right'
|
placement='right'
|
||||||
rootClose='true'
|
rootClose={true}
|
||||||
overlay={<Popover title={this.state.profile.username}>{dataContent}</Popover>}
|
overlay={
|
||||||
|
<Popover
|
||||||
|
title={this.state.profile.username}
|
||||||
|
id='user-profile-popover'
|
||||||
|
>
|
||||||
|
{dataContent}
|
||||||
|
</Popover>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className='user-popover'
|
className='user-popover'
|
||||||
id={'profile_' + this.uniqueId}
|
id={'profile_' + this.uniqueId}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,9 +152,8 @@ export default class UserSettingsAppearance extends React.Component {
|
|||||||
<input type='radio'
|
<input type='radio'
|
||||||
checked={!displayCustom}
|
checked={!displayCustom}
|
||||||
onChange={this.updateType.bind(this, 'premade')}
|
onChange={this.updateType.bind(this, 'premade')}
|
||||||
>
|
/>
|
||||||
{'Theme Colors'}
|
{'Theme Colors'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -164,9 +163,8 @@ export default class UserSettingsAppearance extends React.Component {
|
|||||||
<input type='radio'
|
<input type='radio'
|
||||||
checked={displayCustom}
|
checked={displayCustom}
|
||||||
onChange={this.updateType.bind(this, 'custom')}
|
onChange={this.updateType.bind(this, 'custom')}
|
||||||
>
|
/>
|
||||||
{'Custom Theme'}
|
{'Custom Theme'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import { savePreferences } from '../../utils/client.jsx';
|
import {savePreferences} from '../../utils/client.jsx';
|
||||||
import SettingItemMin from '../setting_item_min.jsx';
|
import SettingItemMin from '../setting_item_min.jsx';
|
||||||
import SettingItemMax from '../setting_item_max.jsx';
|
import SettingItemMax from '../setting_item_max.jsx';
|
||||||
import Constants from '../../utils/constants.jsx';
|
import Constants from '../../utils/constants.jsx';
|
||||||
@@ -38,7 +38,7 @@ export default class UserSettingsDisplay extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
handleClockRadio(militaryTime) {
|
handleClockRadio(militaryTime) {
|
||||||
this.setState({militaryTime: militaryTime});
|
this.setState({militaryTime});
|
||||||
}
|
}
|
||||||
updateSection(section) {
|
updateSection(section) {
|
||||||
this.setState(getDisplayStateFromStores());
|
this.setState(getDisplayStateFromStores());
|
||||||
@@ -57,7 +57,7 @@ export default class UserSettingsDisplay extends React.Component {
|
|||||||
const serverError = this.state.serverError || null;
|
const serverError = this.state.serverError || null;
|
||||||
let clockSection;
|
let clockSection;
|
||||||
if (this.props.activeSection === 'clock') {
|
if (this.props.activeSection === 'clock') {
|
||||||
let clockFormat = [false, false];
|
const clockFormat = [false, false];
|
||||||
if (this.state.militaryTime === 'true') {
|
if (this.state.militaryTime === 'true') {
|
||||||
clockFormat[1] = true;
|
clockFormat[1] = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -77,9 +77,8 @@ export default class UserSettingsDisplay extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={clockFormat[0]}
|
checked={clockFormat[0]}
|
||||||
onChange={this.handleClockRadio.bind(this, 'false')}
|
onChange={this.handleClockRadio.bind(this, 'false')}
|
||||||
>
|
/>
|
||||||
12-hour clock (example: 4:00 PM)
|
{'12-hour clock (example: 4:00 PM)'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,9 +88,8 @@ export default class UserSettingsDisplay extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={clockFormat[1]}
|
checked={clockFormat[1]}
|
||||||
onChange={this.handleClockRadio.bind(this, 'true')}
|
onChange={this.handleClockRadio.bind(this, 'true')}
|
||||||
>
|
/>
|
||||||
24-hour clock (example: 16:00)
|
{'24-hour clock (example: 16:00)'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,7 +97,6 @@ export default class UserSettingsDisplay extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
clockSection = (
|
clockSection = (
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
title='Clock Display'
|
title='Clock Display'
|
||||||
@@ -138,13 +135,13 @@ export default class UserSettingsDisplay extends React.Component {
|
|||||||
className='close'
|
className='close'
|
||||||
data-dismiss='modal'
|
data-dismiss='modal'
|
||||||
aria-label='Close'
|
aria-label='Close'
|
||||||
>
|
>
|
||||||
<span aria-hidden='true'>{'×'}</span>
|
<span aria-hidden='true'>{'×'}</span>
|
||||||
</button>
|
</button>
|
||||||
<h4
|
<h4
|
||||||
className='modal-title'
|
className='modal-title'
|
||||||
ref='title'
|
ref='title'
|
||||||
>
|
>
|
||||||
<i className='modal-back'></i>
|
<i className='modal-back'></i>
|
||||||
{'Display Settings'}
|
{'Display Settings'}
|
||||||
</h4>
|
</h4>
|
||||||
|
|||||||
@@ -228,9 +228,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
<input type='radio'
|
<input type='radio'
|
||||||
checked={notifyActive[0]}
|
checked={notifyActive[0]}
|
||||||
onChange={this.handleNotifyRadio.bind(this, 'all')}
|
onChange={this.handleNotifyRadio.bind(this, 'all')}
|
||||||
>
|
/>
|
||||||
For all activity
|
{'For all activity'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,9 +239,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={notifyActive[1]}
|
checked={notifyActive[1]}
|
||||||
onChange={this.handleNotifyRadio.bind(this, 'mention')}
|
onChange={this.handleNotifyRadio.bind(this, 'mention')}
|
||||||
>
|
/>
|
||||||
Only for mentions and direct messages
|
{'Only for mentions and direct messages'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -252,9 +250,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={notifyActive[2]}
|
checked={notifyActive[2]}
|
||||||
onChange={this.handleNotifyRadio.bind(this, 'none')}
|
onChange={this.handleNotifyRadio.bind(this, 'none')}
|
||||||
>
|
/>
|
||||||
Never
|
{'Never'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -320,9 +317,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={soundActive[0]}
|
checked={soundActive[0]}
|
||||||
onChange={this.handleSoundRadio.bind(this, 'true')}
|
onChange={this.handleSoundRadio.bind(this, 'true')}
|
||||||
>
|
/>
|
||||||
On
|
{'On'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -332,9 +328,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={soundActive[1]}
|
checked={soundActive[1]}
|
||||||
onChange={this.handleSoundRadio.bind(this, 'false')}
|
onChange={this.handleSoundRadio.bind(this, 'false')}
|
||||||
>
|
/>
|
||||||
Off
|
{'Off'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -402,9 +397,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={emailActive[0]}
|
checked={emailActive[0]}
|
||||||
onChange={this.handleEmailRadio.bind(this, 'true')}
|
onChange={this.handleEmailRadio.bind(this, 'true')}
|
||||||
>
|
/>
|
||||||
On
|
{'On'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,9 +408,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='radio'
|
type='radio'
|
||||||
checked={emailActive[1]}
|
checked={emailActive[1]}
|
||||||
onChange={this.handleEmailRadio.bind(this, 'false')}
|
onChange={this.handleEmailRadio.bind(this, 'false')}
|
||||||
>
|
/>
|
||||||
Off
|
{'Off'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
@@ -482,9 +475,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={this.state.firstNameKey}
|
checked={this.state.firstNameKey}
|
||||||
onChange={handleUpdateFirstNameKey}
|
onChange={handleUpdateFirstNameKey}
|
||||||
>
|
/>
|
||||||
{'Your case sensitive first name "' + user.first_name + '"'}
|
{'Your case sensitive first name "' + user.first_name + '"'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -502,9 +494,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={this.state.usernameKey}
|
checked={this.state.usernameKey}
|
||||||
onChange={handleUpdateUsernameKey}
|
onChange={handleUpdateUsernameKey}
|
||||||
>
|
/>
|
||||||
{'Your non-case sensitive username "' + user.username + '"'}
|
{'Your non-case sensitive username "' + user.username + '"'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -521,9 +512,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={this.state.mentionKey}
|
checked={this.state.mentionKey}
|
||||||
onChange={handleUpdateMentionKey}
|
onChange={handleUpdateMentionKey}
|
||||||
>
|
/>
|
||||||
{'Your username mentioned "@' + user.username + '"'}
|
{'Your username mentioned "@' + user.username + '"'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -540,9 +530,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={this.state.allKey}
|
checked={this.state.allKey}
|
||||||
onChange={handleUpdateAllKey}
|
onChange={handleUpdateAllKey}
|
||||||
>
|
/>
|
||||||
{'Team-wide mentions "@all"'}
|
{'Team-wide mentions "@all"'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -559,9 +548,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={this.state.channelKey}
|
checked={this.state.channelKey}
|
||||||
onChange={handleUpdateChannelKey}
|
onChange={handleUpdateChannelKey}
|
||||||
>
|
/>
|
||||||
{'Channel-wide mentions "@channel"'}
|
{'Channel-wide mentions "@channel"'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -576,9 +564,8 @@ export default class NotificationsTab extends React.Component {
|
|||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={this.state.customKeysChecked}
|
checked={this.state.customKeysChecked}
|
||||||
onChange={this.updateCustomMentionKeys}
|
onChange={this.updateCustomMentionKeys}
|
||||||
>
|
/>
|
||||||
{'Other non-case sensitive words, separated by commas:'}
|
{'Other non-case sensitive words, separated by commas:'}
|
||||||
</input>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const Utils = require('../utils/utils.jsx');
|
|||||||
const Constants = require('../utils/constants.jsx');
|
const Constants = require('../utils/constants.jsx');
|
||||||
const ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
|
const ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
|
||||||
const Modal = ReactBootstrap.Modal;
|
const Modal = ReactBootstrap.Modal;
|
||||||
|
const KeyCodes = Constants.KeyCodes;
|
||||||
|
|
||||||
export default class ViewImageModal extends React.Component {
|
export default class ViewImageModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -63,11 +64,11 @@ export default class ViewImageModal extends React.Component {
|
|||||||
this.loadImage(id);
|
this.loadImage(id);
|
||||||
}
|
}
|
||||||
handleKeyPress(e) {
|
handleKeyPress(e) {
|
||||||
if (!e) {
|
if (!e || !this.props.show) {
|
||||||
return;
|
return;
|
||||||
} else if (e.keyCode === 39) {
|
} else if (e.keyCode === KeyCodes.RIGHT) {
|
||||||
this.handleNext();
|
this.handleNext();
|
||||||
} else if (e.keyCode === 37) {
|
} else if (e.keyCode === KeyCodes.LEFT) {
|
||||||
this.handlePrev();
|
this.handlePrev();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ var ErrorBar = require('../components/error_bar.jsx');
|
|||||||
var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx');
|
var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx');
|
||||||
var AdminController = require('../components/admin_console/admin_controller.jsx');
|
var AdminController = require('../components/admin_console/admin_controller.jsx');
|
||||||
|
|
||||||
export function setupAdminConsolePage() {
|
export function setupAdminConsolePage(props) {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AdminController />,
|
<AdminController
|
||||||
|
tab={props.ActiveTab}
|
||||||
|
teamId={props.TeamId}
|
||||||
|
/>,
|
||||||
document.getElementById('admin_controller')
|
document.getElementById('admin_controller')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
var UserStore = require('./user_store.jsx');
|
const UserStore = require('./user_store.jsx');
|
||||||
var ErrorStore = require('./error_store.jsx');
|
const PostStore = require('./post_store.jsx');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
const ChannelStore = require('./channel_store.jsx');
|
||||||
|
const BrowserStore = require('./browser_store.jsx');
|
||||||
|
const ErrorStore = require('./error_store.jsx');
|
||||||
|
const EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
var Constants = require('../utils/constants.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
var ActionTypes = Constants.ActionTypes;
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
|
|
||||||
var CHANGE_EVENT = 'change';
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
const SocketEvents = Constants.SocketEvents;
|
||||||
|
|
||||||
|
const CHANGE_EVENT = 'change';
|
||||||
|
|
||||||
var conn;
|
var conn;
|
||||||
|
|
||||||
@@ -94,6 +101,39 @@ class SocketStoreClass extends EventEmitter {
|
|||||||
removeChangeListener(callback) {
|
removeChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT, callback);
|
this.removeListener(CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
handleMessage(msg) {
|
||||||
|
switch (msg.action) {
|
||||||
|
case SocketEvents.POSTED:
|
||||||
|
handleNewPostEvent(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketEvents.POST_EDITED:
|
||||||
|
handlePostEditEvent(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketEvents.POST_DELETED:
|
||||||
|
handlePostDeleteEvent(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketEvents.NEW_USER:
|
||||||
|
handleNewUserEvent();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketEvents.USER_ADDED:
|
||||||
|
handleUserAddedEvent(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketEvents.USER_REMOVED:
|
||||||
|
handleUserRemovedEvent(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SocketEvents.CHANNEL_VIEWED:
|
||||||
|
handleChannelViewedEvent(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
sendMessage(msg) {
|
sendMessage(msg) {
|
||||||
if (conn && conn.readyState === WebSocket.OPEN) {
|
if (conn && conn.readyState === WebSocket.OPEN) {
|
||||||
conn.send(JSON.stringify(msg));
|
conn.send(JSON.stringify(msg));
|
||||||
@@ -104,6 +144,138 @@ class SocketStoreClass extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleNewPostEvent(msg) {
|
||||||
|
// Store post
|
||||||
|
const post = JSON.parse(msg.props.post);
|
||||||
|
PostStore.storePost(post);
|
||||||
|
|
||||||
|
// Update channel state
|
||||||
|
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||||
|
if (window.isActive) {
|
||||||
|
AsyncClient.updateLastViewedAt();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AsyncClient.getChannel(msg.channel_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send desktop notification
|
||||||
|
if (UserStore.getCurrentId() !== msg.user_id) {
|
||||||
|
const msgProps = msg.props;
|
||||||
|
|
||||||
|
let mentions = [];
|
||||||
|
if (msgProps.mentions) {
|
||||||
|
mentions = JSON.parse(msg.props.mentions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = ChannelStore.get(msg.channel_id);
|
||||||
|
const user = UserStore.getCurrentUser();
|
||||||
|
const member = ChannelStore.getMember(msg.channel_id);
|
||||||
|
|
||||||
|
let notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
|
||||||
|
if (notifyLevel === 'default') {
|
||||||
|
notifyLevel = user.notify_props.desktop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyLevel === 'none') {
|
||||||
|
return;
|
||||||
|
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let username = 'Someone';
|
||||||
|
if (UserStore.hasProfile(msg.user_id)) {
|
||||||
|
username = UserStore.getProfile(msg.user_id).username;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = 'Posted';
|
||||||
|
if (channel) {
|
||||||
|
title = channel.display_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notifyText = post.message.replace(/\n+/g, ' ');
|
||||||
|
if (notifyText.length > 50) {
|
||||||
|
notifyText = notifyText.substring(0, 49) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyText.length === 0) {
|
||||||
|
if (msgProps.image) {
|
||||||
|
Utils.notifyMe(title, username + ' uploaded an image', channel);
|
||||||
|
} else if (msgProps.otherFile) {
|
||||||
|
Utils.notifyMe(title, username + ' uploaded a file', channel);
|
||||||
|
} else {
|
||||||
|
Utils.notifyMe(title, username + ' did something new', channel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
|
||||||
|
}
|
||||||
|
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
|
||||||
|
Utils.ding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePostEditEvent(msg) {
|
||||||
|
// Store post
|
||||||
|
const post = JSON.parse(msg.props.post);
|
||||||
|
PostStore.storePost(post);
|
||||||
|
|
||||||
|
// Update channel state
|
||||||
|
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||||
|
if (window.isActive) {
|
||||||
|
AsyncClient.updateLastViewedAt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePostDeleteEvent(msg) {
|
||||||
|
const post = JSON.parse(msg.props.post);
|
||||||
|
|
||||||
|
PostStore.storeUnseenDeletedPost(post);
|
||||||
|
PostStore.removePost(post, true);
|
||||||
|
PostStore.emitChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewUserEvent() {
|
||||||
|
AsyncClient.getProfiles();
|
||||||
|
AsyncClient.getChannelExtraInfo(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUserAddedEvent(msg) {
|
||||||
|
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||||
|
AsyncClient.getChannelExtraInfo(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UserStore.getCurrentId() === msg.user_id) {
|
||||||
|
AsyncClient.getChannel(msg.channel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUserRemovedEvent(msg) {
|
||||||
|
if (UserStore.getCurrentId() === msg.user_id) {
|
||||||
|
AsyncClient.getChannels();
|
||||||
|
|
||||||
|
if (msg.props.remover_id !== msg.user_id &&
|
||||||
|
msg.channel_id === ChannelStore.getCurrentId() &&
|
||||||
|
$('#removed_from_channel').length > 0) {
|
||||||
|
var sentState = {};
|
||||||
|
sentState.channelName = ChannelStore.getCurrent().display_name;
|
||||||
|
sentState.remover = UserStore.getProfile(msg.props.remover_id).username;
|
||||||
|
|
||||||
|
BrowserStore.setItem('channel-removed-state', sentState);
|
||||||
|
$('#removed_from_channel').modal('show');
|
||||||
|
}
|
||||||
|
} else if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||||
|
AsyncClient.getChannelExtraInfo(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChannelViewedEvent(msg) {
|
||||||
|
// Useful for when multiple devices have the app open to different channels
|
||||||
|
if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
|
||||||
|
AsyncClient.getChannel(msg.channel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var SocketStore = new SocketStoreClass();
|
var SocketStore = new SocketStoreClass();
|
||||||
|
|
||||||
SocketStore.dispatchToken = AppDispatcher.register((payload) => {
|
SocketStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||||
@@ -111,6 +283,7 @@ SocketStore.dispatchToken = AppDispatcher.register((payload) => {
|
|||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.RECIEVED_MSG:
|
case ActionTypes.RECIEVED_MSG:
|
||||||
|
SocketStore.handleMessage(action.msg);
|
||||||
SocketStore.emitChange(action.msg);
|
SocketStore.emitChange(action.msg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,18 @@ module.exports = {
|
|||||||
SERVER_ACTION: null,
|
SERVER_ACTION: null,
|
||||||
VIEW_ACTION: null
|
VIEW_ACTION: null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
SocketEvents: {
|
||||||
|
POSTED: 'posted',
|
||||||
|
POST_EDITED: 'post_edited',
|
||||||
|
POST_DELETED: 'post_deleted',
|
||||||
|
CHANNEL_VIEWED: 'channel_viewed',
|
||||||
|
NEW_USER: 'new_user',
|
||||||
|
USER_ADDED: 'user_added',
|
||||||
|
USER_REMOVED: 'user_removed',
|
||||||
|
TYPING: 'typing'
|
||||||
|
},
|
||||||
|
|
||||||
SPECIAL_MENTIONS: ['all', 'channel'],
|
SPECIAL_MENTIONS: ['all', 'channel'],
|
||||||
CHARACTER_LIMIT: 4000,
|
CHARACTER_LIMIT: 4000,
|
||||||
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
|
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ var client = require('./client.jsx');
|
|||||||
var Autolinker = require('autolinker');
|
var Autolinker = require('autolinker');
|
||||||
|
|
||||||
export function isEmail(email) {
|
export function isEmail(email) {
|
||||||
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
|
//var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
|
||||||
|
var regex = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
|
||||||
return regex.test(email);
|
return regex.test(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
border: $border-gray;
|
border: $border-gray;
|
||||||
bottom: 38px;
|
bottom: 38px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
z-index: 100;
|
||||||
@extend %popover-box-shadow;
|
@extend %popover-box-shadow;
|
||||||
.sidebar--right & {
|
.sidebar--right & {
|
||||||
bottom: 100px;
|
bottom: 100px;
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.more-channel-table {
|
.more-table {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
p {
|
p {
|
||||||
@@ -150,9 +150,11 @@
|
|||||||
@include opacity(0.8);
|
@include opacity(0.8);
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
.more-channel-name {
|
.more-name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
tbody {
|
tbody {
|
||||||
> tr {
|
> tr {
|
||||||
@@ -175,6 +177,9 @@
|
|||||||
padding: 8px 15px 8px 8px;
|
padding: 8px 15px 8px 8px;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
&.lg {
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,47 +336,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-direct-channels {
|
.modal-direct-channels {
|
||||||
.user-list {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 15px 0px 0px;
|
|
||||||
max-height: 600px;
|
|
||||||
padding: 0px;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
li {
|
.user-list {
|
||||||
border-bottom: 1px solid #ddd;
|
margin-top: 20px;
|
||||||
height: 60px;
|
overflow: auto;
|
||||||
padding: 10px 0px;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
max-height: 500px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.image-div {
|
.table {
|
||||||
padding: 0px;
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-image {
|
.modal-body {
|
||||||
width: 40px;
|
padding: 20px 0 0;
|
||||||
height: 40px;
|
@include clearfix;
|
||||||
@include border-radius(20px);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.username {
|
.filter-row {
|
||||||
font-weight: bold;
|
padding: 0 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nickname {
|
.member-count {
|
||||||
color: #888;
|
margin-top: 5px;
|
||||||
}
|
float: right;
|
||||||
|
@include opacity(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-div {
|
.more-description {
|
||||||
padding: 0px;
|
@include opacity(0.7);
|
||||||
.btn-message {
|
}
|
||||||
position: relative;
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
.profile-img {
|
||||||
border-bottom: 0px;
|
-moz-border-radius: 50px;
|
||||||
}
|
-webkit-border-radius: 50px;
|
||||||
}
|
border-radius: 50px;
|
||||||
}
|
margin-right: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,6 +270,13 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.modal-direct-channels {
|
||||||
|
.member-count {
|
||||||
|
float: none;
|
||||||
|
margin-top: 10px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
.center-file-overlay {
|
.center-file-overlay {
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,13 +230,6 @@
|
|||||||
font-weight:500;
|
font-weight:500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-img {
|
|
||||||
width:128px;
|
|
||||||
height:128px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
@include border-radius(128px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sel-btn {
|
.sel-btn {
|
||||||
margin-right:5px;
|
margin-right:5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,6 @@
|
|||||||
padding:0px;
|
padding:0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-uploader {
|
|
||||||
font-size: 13px;
|
|
||||||
margin: 0 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-title {
|
.video-title {
|
||||||
font-size:15px;
|
font-size:15px;
|
||||||
margin-top:3px;
|
margin-top:3px;
|
||||||
@@ -54,4 +49,4 @@
|
|||||||
border-top:36px solid transparent;
|
border-top:36px solid transparent;
|
||||||
border-bottom:36px solid transparent;
|
border-bottom:36px solid transparent;
|
||||||
border-left:60px solid rgba(255,255,255,0.4);
|
border-left:60px solid rgba(255,255,255,0.4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<div id='select_team_modal'></div>
|
<div id='select_team_modal'></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.setup_admin_console_page();
|
window.setup_admin_console_page({{ .Props }});
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
|||||||
14
web/web.go
14
web/web.go
@@ -77,6 +77,9 @@ func InitWeb() {
|
|||||||
mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET")
|
mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET")
|
||||||
|
|
||||||
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
|
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
|
||||||
|
mainrouter.Handle("/admin_console/", api.UserRequired(adminConsole)).Methods("GET")
|
||||||
|
mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}", api.UserRequired(adminConsole)).Methods("GET")
|
||||||
|
mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}/{team:[A-Za-z0-9-]*}", api.UserRequired(adminConsole)).Methods("GET")
|
||||||
|
|
||||||
mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
|
mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
|
||||||
|
|
||||||
@@ -753,6 +756,7 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
|
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
|
||||||
userChan := api.Srv.Store.User().Get(c.Session.UserId)
|
userChan := api.Srv.Store.User().Get(c.Session.UserId)
|
||||||
|
|
||||||
@@ -777,6 +781,16 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
page.User = user
|
page.User = user
|
||||||
page.Team = team
|
page.Team = team
|
||||||
page.Session = &c.Session
|
page.Session = &c.Session
|
||||||
|
=======
|
||||||
|
params := mux.Vars(r)
|
||||||
|
activeTab := params["tab"]
|
||||||
|
teamId := params["team"]
|
||||||
|
|
||||||
|
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
||||||
|
|
||||||
|
page.Props["ActiveTab"] = activeTab
|
||||||
|
page.Props["TeamId"] = teamId
|
||||||
|
>>>>>>> master
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user