summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Godeps/Godeps.json4
-rw-r--r--Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml1
-rw-r--r--Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go8
-rw-r--r--Godeps/_workspace/src/github.com/mssola/user_agent/browser.go18
-rw-r--r--NOTICE.txt31
-rw-r--r--api/context.go19
-rw-r--r--api/file.go14
-rw-r--r--api/templates/error.html2
-rw-r--r--doc/help/enduser/markdown.md152
-rw-r--r--web/react/components/create_comment.jsx6
-rw-r--r--web/react/components/create_post.jsx48
-rw-r--r--web/react/components/delete_channel_modal.jsx3
-rw-r--r--web/react/components/error_bar.jsx85
-rw-r--r--web/react/components/navbar.jsx2
-rw-r--r--web/react/components/post_info.jsx16
-rw-r--r--web/react/components/post_list.jsx16
-rw-r--r--web/react/components/signup_user_complete.jsx2
-rw-r--r--web/react/components/team_signup_password_page.jsx2
-rw-r--r--web/react/components/team_signup_with_sso.jsx2
-rw-r--r--web/react/components/textbox.jsx53
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx2
-rw-r--r--web/react/components/view_image.jsx18
-rw-r--r--web/react/pages/channel.jsx6
-rw-r--r--web/react/stores/error_store.jsx2
-rw-r--r--web/react/stores/socket_store.jsx42
-rw-r--r--web/react/utils/client.jsx2
-rw-r--r--web/react/utils/constants.jsx36
-rw-r--r--web/react/utils/utils.jsx16
-rw-r--r--web/sass-files/sass/partials/_loading.scss2
-rw-r--r--web/sass-files/sass/partials/_markdown.scss38
-rw-r--r--web/sass-files/sass/partials/_mentions.scss4
-rw-r--r--web/sass-files/sass/partials/_post.scss7
-rw-r--r--web/sass-files/sass/partials/_videos.scss10
-rw-r--r--web/static/images/themes/mattermost dark.png (renamed from web/static/images/themes/dark.png)bin75371 -> 75371 bytes
-rw-r--r--web/static/images/themes/windows 10 dark.pngbin0 -> 82784 bytes
-rw-r--r--web/web.go10
36 files changed, 508 insertions, 171 deletions
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index d6503a1d5..9bd02d455 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -85,8 +85,8 @@
},
{
"ImportPath": "github.com/mssola/user_agent",
- "Comment": "v0.4.1-2-g35c7f18",
- "Rev": "35c7f18f5261cc18c698a461053c119aebaf8542"
+ "Comment": "v0.4.1-4-ga163d6a",
+ "Rev": "a163d6a569f1cd264d2f8b2bf3c5d04ace5995eb"
},
{
"ImportPath": "github.com/nfnt/resize",
diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml b/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml
index 922091263..33c596acb 100644
--- a/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml
+++ b/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml
@@ -5,6 +5,7 @@ go:
- 1.2
- 1.3
- 1.4
+ - 1.5
- tip
matrix:
allow_failures:
diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go b/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go
index 6dca19d5c..34ccbb864 100644
--- a/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go
+++ b/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go
@@ -40,6 +40,10 @@ var uastrings = []struct {
{"IE11b32Win7b64MDDRJS", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MDDRJS; rv:11.0) like Gecko"},
{"IE11Compatibility", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0)"},
+ // Microsoft Edge
+ {"EdgeDesktop", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"},
+ {"EdgeMobile", "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; DEVICE INFO) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Edge/12.10240"},
+
// Gecko
{"FirefoxMac", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8"},
{"FirefoxMacLoc", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13"},
@@ -129,6 +133,10 @@ var expected = []string{
"Mozilla:5.0 Platform:Windows OS:Windows 7 Browser:Internet Explorer-11.0 Engine:Trident Bot:false Mobile:false",
"Mozilla:4.0 Platform:Windows OS:Windows 8.1 Browser:Internet Explorer-7.0 Engine:Trident Bot:false Mobile:false",
+ // Microsoft Edge
+ "Mozilla:5.0 Platform:Windows OS:Windows NT 10.0 Browser:Edge-12.10240 Engine:EdgeHTML Bot:false Mobile:false",
+ "Mozilla:5.0 Platform:Windows OS:Windows Phone 10.0 Browser:Edge-12.10240 Engine:EdgeHTML Bot:false Mobile:true",
+
// Gecko
"Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10.6 Browser:Firefox-4.0b8 Engine:Gecko-20100101 Bot:false Mobile:false",
"Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10.6 Localization:en-US Browser:Firefox-3.6.13 Engine:Gecko-20101203 Bot:false Mobile:false",
diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go b/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go
index 9fb27e5f3..c5612db7b 100644
--- a/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go
+++ b/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go
@@ -48,13 +48,21 @@ func (p *UserAgent) detectBrowser(sections []section) {
if slen > 2 {
p.browser.Version = sections[2].version
if engine.name == "AppleWebKit" {
- if sections[slen-1].name == "OPR" {
+ switch sections[slen-1].name {
+ case "Edge":
+ p.browser.Name = "Edge"
+ p.browser.Version = sections[slen-1].version
+ p.browser.Engine = "EdgeHTML"
+ p.browser.EngineVersion = ""
+ case "OPR":
p.browser.Name = "Opera"
p.browser.Version = sections[slen-1].version
- } else if sections[2].name == "Chrome" {
- p.browser.Name = "Chrome"
- } else {
- p.browser.Name = "Safari"
+ default:
+ if sections[2].name == "Chrome" {
+ p.browser.Name = "Chrome"
+ } else {
+ p.browser.Name = "Safari"
+ }
}
} else if engine.name == "Gecko" {
name := sections[2].name
diff --git a/NOTICE.txt b/NOTICE.txt
index dafc6d257..b7a7fbc1d 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -767,3 +767,34 @@ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+
+---
+
+This product contains a modified portion of 'marked', a full-featured markdown parser and compiler, written in JavaScript. Built for speed.
+
+by Christopher Jeffrey
+
+* HOMEPAGE:
+ * https://github.com/chjj/marked
+
+* LICENSE:
+
+Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/api/context.go b/api/context.go
index 9a276a1a1..d90fbd9ee 100644
--- a/api/context.go
+++ b/api/context.go
@@ -456,18 +456,19 @@ func IsPrivateIpAddress(ipAddress string) bool {
}
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
+ props := make(map[string]string)
+ props["Message"] = err.Message
+ props["Details"] = err.DetailedError
- protocol := GetProtocol(r)
- SiteURL := protocol + "://" + r.Host
-
- m := make(map[string]string)
- m["Message"] = err.Message
- m["Details"] = err.DetailedError
- m["SiteName"] = utils.Cfg.TeamSettings.SiteName
- m["SiteURL"] = SiteURL
+ pathParts := strings.Split(r.URL.Path, "/")
+ if len(pathParts) > 1 {
+ props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1]
+ } else {
+ props["SiteURL"] = GetProtocol(r) + "://" + r.Host
+ }
w.WriteHeader(err.StatusCode)
- ServerTemplates.ExecuteTemplate(w, "error.html", m)
+ ServerTemplates.ExecuteTemplate(w, "error.html", Page{Props: props, ClientProps: utils.ClientProperties})
}
func Handle404(w http.ResponseWriter, r *http.Request) {
diff --git a/api/file.go b/api/file.go
index 694fc734c..1cb05e81b 100644
--- a/api/file.go
+++ b/api/file.go
@@ -13,6 +13,7 @@ import (
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
+ "github.com/mssola/user_agent"
"github.com/nfnt/resize"
"github.com/rwcarlsen/goexif/exif"
_ "golang.org/x/image/bmp"
@@ -426,6 +427,19 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=2592000, public")
w.Header().Set("Content-Length", strconv.Itoa(len(f)))
w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(filename)))
+
+ // attach extra headers to trigger a download on IE and Edge
+ ua := user_agent.New(r.UserAgent())
+ bname, _ := ua.Browser()
+
+ if bname == "Edge" || bname == "Internet Explorer" {
+ // trim off anything before the final / so we just get the file's name
+ parts := strings.Split(filename, "/")
+
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.Header().Set("Content-Disposition", "attachment;filename=\""+parts[len(parts)-1]+"\"")
+ }
+
w.Write(f)
}
diff --git a/api/templates/error.html b/api/templates/error.html
index 760578896..6b643556e 100644
--- a/api/templates/error.html
+++ b/api/templates/error.html
@@ -23,7 +23,7 @@
<div class="error__container">
<div class="error__icon"><i class="fa fa-exclamation-triangle"></i></div>
<h2>{{ .ClientProps.SiteName }} needs your help:</h2>
- <p>{{.Message}}</p>
+ <p>{{ .Props.Message }}</p>
<a href="{{.Props.SiteURL}}">Go back to team site</a>
</div>
</div>
diff --git a/doc/help/enduser/markdown.md b/doc/help/enduser/markdown.md
new file mode 100644
index 000000000..9e2342a0b
--- /dev/null
+++ b/doc/help/enduser/markdown.md
@@ -0,0 +1,152 @@
+# Markdown Help
+
+Markdown makes it easy to format messages. Type a message as you normally would, and use these rules to render it with special formatting.
+
+## Text Style:
+
+You can use either `_` or `*` around a word to make it italic. Use two to make it bold.
+
+* `_italics_` renders as _italics_
+* `**bold**` renders as **bold**
+* `**_bold-italic_**` renders as **_bold-italics_**
+* `~~strikethrough~~` renders as ~~strikethrough~~
+
+## Code:
+
+Create a code block by indenting four spaces, or by placing ``` on the line above and below your code.
+
+Example:
+
+ ```
+ code block
+ ```
+
+Renders as:
+```
+code block
+```
+
+Create in-line monospaced font by surrounding it with back spaces.
+```
+`monospace`
+```
+Renders as: `monospace`.
+
+## Links:
+
+Create labeled links by putting the desired text in square brackets and the associated link in normal brackets.
+
+`[Check out Mattermost!](www.mattermost.com)`
+
+Renders as: [Check out Mattermost!](www.mattermost.com)
+
+## In-line Images
+
+Create in-line images using an `!` followed by the alt text in square brackets and the link in normal brackets. Add hover text by placing it in quotes after the link.
+```
+![alt text](link "hover text")
+
+and
+
+[![Build Status](https://travis-ci.org/mattermost/platform.svg?branch=master)](https://travis-ci.org/mattermost/platform) [![Github](https://assets-cdn.github.com/favicon.ico)](https://github.com/mattermost/platform)
+```
+Renders as:
+
+![alt text](link "hover text")
+
+and
+
+[![Build Status](https://travis-ci.org/mattermost/platform.svg?branch=master)](https://travis-ci.org/mattermost/platform) [![Github](https://assets-cdn.github.com/favicon.ico)](https://github.com/mattermost/platform)
+
+## Emojis
+
+Check out a full list of emojis [here](http://www.emoji-cheat-sheet.com/).
+
+```
+:smile: :+1: :sheep:
+```
+Renders as:
+:smile: :+1: :sheep:
+
+## Lines:
+
+Create a line by using three `*`, `_`, or `-`.
+
+`***` renders as:
+***
+
+## Block quotes:
+
+Create block quotes using `>`.
+
+`> block quotes` renders as:
+> block quotes
+
+## Lists:
+
+Create a list by using `*` or `-` as bullets. Indent a bullet point by adding two spaces in front of it.
+```
+* list item one
+* list item two
+ * item two sub-point
+```
+Renders as:
+* list item one
+* list item two
+ * item two sub-point
+
+Make it an ordered list by using numbers instead:
+```
+1. Item one
+2. Item two
+```
+Renders as:
+1. Item one
+2. Item two
+
+## Tables:
+
+Create a table by placing a dashed line under the header row and separating the columns with a pipe `|`. (The columns don’t need to line up exactly for it to work). Choose how to align table columns by including colons `:` within the header row.
+```
+| Left-Aligned  | Center Aligned  | Right Aligned |
+| :------------ |:---------------:| -----:|
+| Left column 1 | this text       |  $100 |
+| Left column 2 | is              |   $10 |
+| Left column 3 | centered        |    $1 |
+```
+
+Renders as:
+
+| Left-Aligned  | Center Aligned  | Right Aligned |
+| :------------ |:---------------:| -----:|
+| Left column 1 | this text       |  $100 |
+| Left column 2 | is              |   $10 |
+| Left column 3 | centered        |    $1 |
+
+## Headings:
+
+Make a heading by typing # and a space before your title. For smaller headings, use more #’s.
+```
+# Large heading
+## Smaller heading
+### Even smaller heading
+```
+Renders as:
+# Large Heading
+## Smaller Heading
+### Even smaller heading
+
+Alternatively, for the large heading you can underline the text using `===`. For the smaller heading you can underline using `---`
+```
+Large Heading
+=============
+
+Smaller Heading
+--------------
+```
+Renders as:
+Large Heading
+=============
+
+Smaller Heading
+--------------
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index 99f553c0c..5097b3aa5 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -43,6 +43,12 @@ export default class CreateComment extends React.Component {
submitting: false
};
}
+ componentDidUpdate(prevProps, prevState) {
+ if (prevState.uploadsInProgress < this.state.uploadsInProgress) {
+ $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
+ $('.post-right__scroll').perfectScrollbar('update');
+ }
+ }
handleSubmit(e) {
e.preventDefault();
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 595643027..0cd14747d 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -52,6 +52,12 @@ export default class CreatePost extends React.Component {
componentDidUpdate(prevProps, prevState) {
if (prevState.previews.length !== this.state.previews.length) {
this.resizePostHolder();
+ return;
+ }
+
+ if (prevState.uploadsInProgress !== this.state.uploadsInProgress) {
+ this.resizePostHolder();
+ return;
}
}
getCurrentDraft() {
@@ -79,7 +85,7 @@ export default class CreatePost extends React.Component {
return;
}
- let post = {};
+ const post = {};
post.filenames = [];
post.message = this.state.messageText;
@@ -99,20 +105,20 @@ export default class CreatePost extends React.Component {
this.state.channelId,
post.message,
false,
- function handleCommandSuccess(data) {
+ (data) => {
PostStore.storeDraft(data.channel_id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
if (data.goto_location.length > 0) {
window.location.href = data.goto_location;
}
- }.bind(this),
- function handleCommandError(err) {
- let state = {};
+ },
+ (err) => {
+ const state = {};
state.serverError = err.message;
state.submitting = false;
this.setState(state);
- }.bind(this)
+ }
);
} else {
post.channel_id = this.state.channelId;
@@ -133,10 +139,10 @@ export default class CreatePost extends React.Component {
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
Client.createPost(post, channel,
- function handlePostSuccess(data) {
+ (data) => {
AsyncClient.getPosts();
- let member = ChannelStore.getMember(channel.id);
+ const member = ChannelStore.getMember(channel.id);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
@@ -146,8 +152,8 @@ export default class CreatePost extends React.Component {
post: data
});
},
- function handlePostError(err) {
- let state = {};
+ (err) => {
+ const state = {};
if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) {
@@ -161,7 +167,7 @@ export default class CreatePost extends React.Component {
state.submitting = false;
this.setState(state);
- }.bind(this)
+ }
);
}
}
@@ -179,9 +185,9 @@ export default class CreatePost extends React.Component {
}
}
handleUserInput(messageText) {
- this.setState({messageText: messageText});
+ this.setState({messageText});
- let draft = PostStore.getCurrentDraft();
+ const draft = PostStore.getCurrentDraft();
draft.message = messageText;
PostStore.storeCurrentDraft(draft);
}
@@ -191,7 +197,7 @@ export default class CreatePost extends React.Component {
$(window).trigger('resize');
}
handleUploadStart(clientIds, channelId) {
- let draft = PostStore.getDraft(channelId);
+ const draft = PostStore.getDraft(channelId);
draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
PostStore.storeDraft(channelId, draft);
@@ -199,7 +205,7 @@ export default class CreatePost extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress});
}
handleFileUploadComplete(filenames, clientIds, channelId) {
- let draft = PostStore.getDraft(channelId);
+ const draft = PostStore.getDraft(channelId);
// remove each finished file from uploads
for (let i = 0; i < clientIds.length; i++) {
@@ -217,7 +223,7 @@ export default class CreatePost extends React.Component {
}
handleUploadError(err, clientId) {
if (clientId !== -1) {
- let draft = PostStore.getDraft(this.state.channelId);
+ const draft = PostStore.getDraft(this.state.channelId);
const index = draft.uploadsInProgress.indexOf(clientId);
if (index !== -1) {
@@ -237,8 +243,8 @@ export default class CreatePost extends React.Component {
Utils.setCaretPosition(React.findDOMNode(this.refs.textbox.refs.message), newText.length);
}
removePreview(id) {
- let previews = this.state.previews;
- let uploadsInProgress = this.state.uploadsInProgress;
+ const previews = Object.assign([], this.state.previews);
+ const uploadsInProgress = this.state.uploadsInProgress;
// id can either be the path of an uploaded file or the client id of an in progress upload
let index = previews.indexOf(id);
@@ -253,12 +259,12 @@ export default class CreatePost extends React.Component {
}
}
- let draft = PostStore.getCurrentDraft();
+ const draft = PostStore.getCurrentDraft();
draft.previews = previews;
draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCurrentDraft(draft);
- this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
+ this.setState({previews, uploadsInProgress});
}
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
@@ -272,7 +278,7 @@ export default class CreatePost extends React.Component {
if (this.state.channelId !== channelId) {
const draft = this.getCurrentDraft();
- this.setState({channelId: channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
+ this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
}
}
getFileCount(channelId) {
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index 4efb9cb23..44c54db72 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -4,6 +4,7 @@
const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
export default class DeleteChannelModal extends React.Component {
constructor(props) {
@@ -24,7 +25,7 @@ export default class DeleteChannelModal extends React.Component {
Client.deleteChannel(this.state.channelId,
function handleDeleteSuccess() {
AsyncClient.getChannels(true);
- window.location.href = '/';
+ window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
},
function handleDeleteError(err) {
AsyncClient.dispatchError(err, 'handleDelete');
diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx
index 87d94a41d..05726e860 100644
--- a/web/react/components/error_bar.jsx
+++ b/web/react/components/error_bar.jsx
@@ -2,10 +2,6 @@
// See License.txt for license information.
var ErrorStore = require('../stores/error_store.jsx');
-var utils = require('../utils/utils.jsx');
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
export default class ErrorBar extends React.Component {
constructor() {
@@ -13,70 +9,79 @@ export default class ErrorBar extends React.Component {
this.onErrorChange = this.onErrorChange.bind(this);
this.handleClose = this.handleClose.bind(this);
+ this.prevTimer = null;
- this.state = this.getStateFromStores();
- if (this.state.message) {
- setTimeout(this.handleClose, 10000);
+ this.state = ErrorStore.getLastError();
+ if (this.state && this.state.message) {
+ this.prevTimer = setTimeout(this.handleClose, 10000);
}
}
- getStateFromStores() {
- var error = ErrorStore.getLastError();
- if (!error || error.message === 'There appears to be a problem with your internet connection') {
- return {message: null};
- }
- return {message: error.message};
- }
componentDidMount() {
ErrorStore.addChangeListener(this.onErrorChange);
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
- $(window).resize(function onResize() {
- if (this.state.message) {
+ $(window).resize(() => {
+ if (this.state && this.state.message) {
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
}
- }.bind(this));
+ });
}
+
componentWillUnmount() {
ErrorStore.removeChangeListener(this.onErrorChange);
}
+
onErrorChange() {
- var newState = this.getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- if (newState.message) {
- setTimeout(this.handleClose, 10000);
- }
+ var newState = ErrorStore.getLastError();
+
+ if (this.prevTimer != null) {
+ clearInterval(this.prevTimer);
+ this.prevTimer = null;
+ }
+ if (newState) {
this.setState(newState);
+ this.prevTimer = setTimeout(this.handleClose, 10000);
+ } else {
+ this.setState({message: null});
}
}
+
handleClose(e) {
if (e) {
e.preventDefault();
}
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_ERROR,
- err: null
- });
+ ErrorStore.storeLastError(null);
+ ErrorStore.emitChange();
$('body').css('padding-top', '0');
}
+
render() {
- if (this.state.message) {
- return (
- <div className='error-bar'>
- <span>{this.state.message}</span>
- <a
- href='#'
- className='error-bar__close'
- onClick={this.handleClose}
- >
- &times;
- </a>
- </div>
- );
+ if (!this.state) {
+ return <div/>;
+ }
+
+ if (!this.state.message) {
+ return <div/>;
+ }
+
+ if (this.state.connErrorCount < 7) {
+ return <div/>;
}
- return <div/>;
+ return (
+ <div className='error-bar'>
+ <span>{this.state.message}</span>
+ <a
+ href='#'
+ className='error-bar__close'
+ onClick={this.handleClose}
+ >
+ &times;
+ </a>
+ </div>
+ );
}
}
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index da9874b0b..bdb50cd9e 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -262,7 +262,7 @@ export default class Navbar extends React.Component {
return (
<div className='navbar-brand'>
<a
- href='/'
+ href={TeamStore.getCurrentTeamUrl() + '/channels/town-square'}
className='heading'
>
{channelTitle}
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index c38edf6a2..824e7ef39 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -5,6 +5,8 @@ var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
+var Tooltip = ReactBootstrap.Tooltip;
+var OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class PostInfo extends React.Component {
constructor(props) {
@@ -148,15 +150,19 @@ export default class PostInfo extends React.Component {
var dropdown = this.createDropdown();
+ let tooltip = <Tooltip>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>;
+
return (
<ul className='post-header post-info'>
<li className='post-header-col'>
- <time
- className='post-profile-time'
- title={`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}
+ <OverlayTrigger
+ placement='top'
+ overlay={tooltip}
>
- {utils.displayDateTime(post.create_at)}
- </time>
+ <time className='post-profile-time'>
+ {utils.displayDateTime(post.create_at)}
+ </time>
+ </OverlayTrigger>
</li>
<li className='post-header-col post-header__reply'>
<div className='dropdown'>
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 218922b67..3e1e075bb 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -105,18 +105,18 @@ export default class PostList extends React.Component {
UserStore.addStatusesChangeListener(this.onTimeChange);
SocketStore.addChangeListener(this.onSocketChange);
- var postHolder = $(React.findDOMNode(this.refs.postlist));
+ const postHolder = $(React.findDOMNode(this.refs.postlist));
- $(window).on('resize.' + this.props.channelId, function resize() {
+ $(window).resize(() => {
this.resize();
if (!this.scrolled) {
this.scrollToBottom();
}
- }.bind(this));
+ });
- postHolder.on('scroll', function scroll() {
- var position = postHolder.scrollTop() + postHolder.height() + 14;
- var bottom = postHolder[0].scrollHeight;
+ postHolder.on('scroll', () => {
+ const position = postHolder.scrollTop() + postHolder.height() + 14;
+ const bottom = postHolder[0].scrollHeight;
if (position >= bottom) {
this.scrolled = false;
@@ -128,7 +128,7 @@ export default class PostList extends React.Component {
this.userHasSeenNew = true;
}
this.isUserScroll = true;
- }.bind(this));
+ });
$('.post-list__content div .post').removeClass('post--last');
$('.post-list__content div:last-child .post').addClass('post--last');
@@ -146,7 +146,7 @@ export default class PostList extends React.Component {
UserStore.removeStatusesChangeListener(this.onTimeChange);
SocketStore.removeChangeListener(this.onSocketChange);
$('body').off('click.userpopover');
- $(window).off('resize.' + this.props.channelId);
+ $(window).off('resize');
var postHolder = $(React.findDOMNode(this.refs.postlist));
postHolder.off('scroll');
}
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 8311747ee..495159efc 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -84,7 +84,7 @@ export default class SignupUserComplete extends React.Component {
if (this.props.hash > 0) {
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
}
- window.location.href = '/';
+ window.location.href = '/' + this.props.teamName + '/channels/town-square';
}.bind(this),
function emailLoginFailure(err) {
if (err.message === 'Login failed because email address has not been verified') {
diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx
index b26d9f6ce..105e4817a 100644
--- a/web/react/components/team_signup_password_page.jsx
+++ b/web/react/components/team_signup_password_page.jsx
@@ -53,7 +53,7 @@ export default class TeamSignupPasswordPage extends React.Component {
props.state.wizard = 'finished';
props.updateParent(props.state, true);
- window.location.href = '/';
+ window.location.href = '/' + teamSignup.team.name + '/channels/town-square';
}.bind(this),
function loginFail(err) {
if (err.message === 'Login failed because email address has not been verified') {
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
index 2849b4cbb..a4972dd8d 100644
--- a/web/react/components/team_signup_with_sso.jsx
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -42,7 +42,7 @@ export default class SSOSignUpPage extends React.Component {
if (data.follow_link) {
window.location.href = data.follow_link;
} else {
- window.location.href = '/';
+ window.location.href = '/' + team.name + '/channels/town-square';
}
},
function fail(err) {
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index ea8126bec..5f5316013 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -5,7 +5,6 @@ const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
const PostStore = require('../stores/post_store.jsx');
const CommandList = require('./command_list.jsx');
const ErrorStore = require('../stores/error_store.jsx');
-const AsyncClient = require('../utils/async_client.jsx');
const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
@@ -18,7 +17,6 @@ export default class Textbox extends React.Component {
this.getStateFromStores = this.getStateFromStores.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.onRecievedError = this.onRecievedError.bind(this);
- this.onTimerInterrupt = this.onTimerInterrupt.bind(this);
this.updateMentionTab = this.updateMentionTab.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
@@ -35,8 +33,7 @@ export default class Textbox extends React.Component {
this.state = {
mentionText: '-1',
mentions: [],
- connection: '',
- timerInterrupt: null
+ connection: ''
};
this.caret = -1;
@@ -44,6 +41,7 @@ export default class Textbox extends React.Component {
this.doProcessMentions = false;
this.mentions = [];
}
+
getStateFromStores() {
const error = ErrorStore.getLastError();
@@ -53,6 +51,7 @@ export default class Textbox extends React.Component {
return {message: null};
}
+
componentDidMount() {
PostStore.addAddMentionListener(this.onListenerChange);
ErrorStore.addChangeListener(this.onRecievedError);
@@ -60,46 +59,28 @@ export default class Textbox extends React.Component {
this.resize();
this.updateMentionTab(null);
}
+
componentWillUnmount() {
PostStore.removeAddMentionListener(this.onListenerChange);
ErrorStore.removeChangeListener(this.onRecievedError);
}
+
onListenerChange(id, username) {
if (id === this.props.id) {
this.addMention(username);
}
}
- onRecievedError() {
- const errorState = this.getStateFromStores();
- if (this.state.timerInterrupt !== null) {
- window.clearInterval(this.state.timerInterrupt);
- this.setState({timerInterrupt: null});
- }
+ onRecievedError() {
+ const errorState = ErrorStore.getLastError();
- if (errorState.message === 'There appears to be a problem with your internet connection') {
+ if (errorState && errorState.connErrorCount > 0) {
this.setState({connection: 'bad-connection'});
- const timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000);
- this.setState({timerInterrupt: timerInterrupt});
} else {
this.setState({connection: ''});
}
}
- onTimerInterrupt() {
- // Since these should only happen when you have no connection and slightly briefly after any
- // performance hit should not matter
- if (this.state.connection === 'bad-connection') {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_ERROR,
- err: null
- });
-
- AsyncClient.updateLastViewedAt();
- }
- window.clearInterval(this.state.timerInterrupt);
- this.setState({timerInterrupt: null});
- }
componentDidUpdate() {
if (this.caret >= 0) {
Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret);
@@ -111,6 +92,7 @@ export default class Textbox extends React.Component {
}
this.resize();
}
+
componentWillReceiveProps(nextProps) {
if (!this.addedMention) {
this.checkForNewMention(nextProps.messageText);
@@ -122,19 +104,22 @@ export default class Textbox extends React.Component {
this.addedMention = false;
this.refs.commands.getSuggestedCommands(nextProps.messageText);
}
+
updateMentionTab(mentionText) {
// using setTimeout so dispatch isn't called during an in progress dispatch
- setTimeout(function updateMentionTabAfterTimeout() {
+ setTimeout(() => {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_MENTION_DATA,
id: this.props.id,
mention_text: mentionText
});
- }.bind(this), 1);
+ }, 1);
}
+
handleChange() {
this.props.onUserInput(React.findDOMNode(this.refs.message).value);
}
+
handleKeyPress(e) {
const text = React.findDOMNode(this.refs.message).value;
@@ -157,6 +142,7 @@ export default class Textbox extends React.Component {
this.props.onKeyPress(e);
}
+
handleKeyDown(e) {
if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') {
this.doProcessMentions = true;
@@ -166,6 +152,7 @@ export default class Textbox extends React.Component {
this.handleBackspace(e);
}
}
+
handleBackspace() {
const text = React.findDOMNode(this.refs.message).value;
if (text.indexOf('/') === 0) {
@@ -185,6 +172,7 @@ export default class Textbox extends React.Component {
this.doProcessMentions = true;
}
}
+
checkForNewMention(text) {
const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
@@ -211,6 +199,7 @@ export default class Textbox extends React.Component {
const name = preText.substring(atIndex + 1, preText.length).toLowerCase();
this.updateMentionTab(name);
}
+
addMention(name) {
const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
@@ -233,11 +222,13 @@ export default class Textbox extends React.Component {
this.props.onUserInput(`${prefix}@${name} ${suffix}`);
}
+
addCommand(cmd) {
const elm = React.findDOMNode(this.refs.message);
elm.value = cmd;
this.handleChange();
}
+
resize() {
const e = React.findDOMNode(this.refs.message);
const w = React.findDOMNode(this.refs.wrapper);
@@ -264,21 +255,25 @@ export default class Textbox extends React.Component {
this.props.onHeightChange();
}
}
+
handleFocus() {
const elm = React.findDOMNode(this.refs.message);
if (elm.title === elm.value) {
elm.value = '';
}
}
+
handleBlur() {
const elm = React.findDOMNode(this.refs.message);
if (elm.value === '') {
elm.value = elm.title;
}
}
+
handlePaste() {
this.doProcessMentions = true;
}
+
render() {
return (
<div
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index 12c041c7f..1bbfbd162 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -134,7 +134,7 @@ export default class ManageIncomingHooks extends React.Component {
} else if (hooks.length > 0) {
displayHooks = hooks;
} else {
- displayHooks = <label>{' None'}</label>;
+ displayHooks = <label>{': None'}</label>;
}
const existingHooks = (
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index e645878c1..a7fecb689 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -1,10 +1,11 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var Client = require('../utils/client.jsx');
-var Utils = require('../utils/utils.jsx');
-var ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
-var Modal = ReactBootstrap.Modal;
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
+const ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
+const Modal = ReactBootstrap.Modal;
export default class ViewImageModal extends React.Component {
constructor(props) {
@@ -200,11 +201,20 @@ export default class ViewImageModal extends React.Component {
</a>
);
} else if (fileType === 'video' || fileType === 'audio') {
+ let width = Constants.WEB_VIDEO_WIDTH;
+ let height = Constants.WEB_VIDEO_HEIGHT;
+ if (Utils.isMobile()) {
+ width = Constants.MOBILE_VIDEO_WIDTH;
+ height = Constants.MOBILE_VIDEO_HEIGHT;
+ }
+
content = (
<video
ref='video'
data-setup='{}'
controls='controls'
+ width={width}
+ height={height}
>
<source src={Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename} />
</video>
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 07207c556..74259194a 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -26,6 +26,7 @@ var ChannelInviteModal = require('../components/channel_invite_modal.jsx');
var TeamMembersModal = require('../components/team_members.jsx');
var DirectChannelModal = require('../components/more_direct_channels.jsx');
var ErrorBar = require('../components/error_bar.jsx');
+var ErrorStore = require('../stores/error_store.jsx');
var ChannelLoader = require('../components/channel_loader.jsx');
var MentionList = require('../components/mention_list.jsx');
var ChannelInfoModal = require('../components/channel_info_modal.jsx');
@@ -234,6 +235,11 @@ function setupChannelPage(props) {
<RegisterAppModal />,
document.getElementById('register_app_modal')
);
+
+ if (global.window.config.SendEmailNotifications === 'false') {
+ ErrorStore.storeLastError({message: 'Preview Mode: Email notifications have not been configured'});
+ ErrorStore.emitChange();
+ }
}
global.window.setup_channel_page = setupChannelPage;
diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx
index 597c88cff..ece7d8522 100644
--- a/web/react/stores/error_store.jsx
+++ b/web/react/stores/error_store.jsx
@@ -48,7 +48,7 @@ class ErrorStoreClass extends EventEmitter {
var ErrorStore = new ErrorStoreClass();
-ErrorStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+ErrorStore.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
switch (action.type) {
case ActionTypes.RECIEVED_ERROR:
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index ae74059d1..1d853f979 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -3,6 +3,7 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var UserStore = require('./user_store.jsx');
+var ErrorStore = require('./error_store.jsx');
var EventEmitter = require('events').EventEmitter;
var Constants = require('../utils/constants.jsx');
@@ -21,6 +22,7 @@ class SocketStoreClass extends EventEmitter {
this.addChangeListener = this.addChangeListener.bind(this);
this.removeChangeListener = this.removeChangeListener.bind(this);
this.sendMessage = this.sendMessage.bind(this);
+ this.failCount = 0;
this.initialize();
}
@@ -37,27 +39,43 @@ class SocketStoreClass extends EventEmitter {
protocol = 'wss://';
}
var connUrl = protocol + location.host + '/api/v1/websocket';
- console.log('connecting to ' + connUrl); //eslint-disable-line no-console
+ if (this.failCount === 0) {
+ console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
+ }
conn = new WebSocket(connUrl);
- conn.onclose = function closeConn(evt) {
- console.log('websocket closed'); //eslint-disable-line no-console
- console.log(evt); //eslint-disable-line no-console
+ conn.onopen = () => {
+ if (this.failCount > 0) {
+ console.log('websocket re-established connection'); //eslint-disable-line no-console
+ }
+
+ this.failCount = 0;
+ ErrorStore.storeLastError(null);
+ ErrorStore.emitChange();
+ };
+
+ conn.onclose = () => {
conn = null;
setTimeout(
- function reconnect() {
+ () => {
this.initialize();
- }.bind(this),
+ },
3000
);
- }.bind(this);
+ };
+
+ conn.onerror = (evt) => {
+ if (this.failCount === 0) {
+ console.log('websocket error ' + evt); //eslint-disable-line no-console
+ }
+
+ this.failCount = this.failCount + 1;
- conn.onerror = function connError(evt) {
- console.log('websocket error'); //eslint-disable-line no-console
- console.log(evt); //eslint-disable-line no-console
+ ErrorStore.storeLastError({connErrorCount: this.failCount, message: 'We cannot reach the Mattermost service. The service may be down or misconfigured. Please contact an administrator to make sure the WebSocket port is configured properly.'});
+ ErrorStore.emitChange();
};
- conn.onmessage = function connMessage(evt) {
+ conn.onmessage = (evt) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_MSG,
msg: JSON.parse(evt.data)
@@ -86,7 +104,7 @@ class SocketStoreClass extends EventEmitter {
var SocketStore = new SocketStoreClass();
-SocketStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+SocketStore.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
switch (action.type) {
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 4effa7307..715e26197 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -27,7 +27,7 @@ function handleError(methodName, xhr, status, err) {
msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err;
if (xhr.status === 0) {
- e = {message: 'There appears to be a problem with your internet connection'};
+ e = {message: 'There appears to be a problem with your internet connection', connErrorCount: 1};
} else {
e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'};
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 8c9e1ee85..7497d8450 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -72,6 +72,10 @@ module.exports = {
MAX_FILE_SIZE: 50000000, // 50 MB
THUMBNAIL_WIDTH: 128,
THUMBNAIL_HEIGHT: 100,
+ WEB_VIDEO_WIDTH: 640,
+ WEB_VIDEO_HEIGHT: 480,
+ MOBILE_VIDEO_WIDTH: 480,
+ MOBILE_VIDEO_HEIGHT: 360,
DEFAULT_CHANNEL: 'town-square',
OFFTOPIC_CHANNEL: 'off-topic',
GITLAB_SERVICE: 'gitlab',
@@ -130,6 +134,7 @@ module.exports = {
mentionColor: '#ffffff',
centerChannelBg: '#ffffff',
centerChannelColor: '#333333',
+ newMessageSeparator: '#FF8800',
linkColor: '#2389d7',
buttonBg: '#2389d7',
buttonColor: '#FFFFFF'
@@ -150,12 +155,13 @@ module.exports = {
mentionColor: '#bfcde8',
centerChannelBg: '#f2f4f8',
centerChannelColor: '#333333',
+ newMessageSeparator: '#FF8800',
linkColor: '#2f81b7',
buttonBg: '#1dacfc',
buttonColor: '#FFFFFF'
},
- dark: {
- type: 'Dark',
+ mattermostDark: {
+ type: 'Mattermost Dark',
sidebarBg: '#1B2C3E',
sidebarText: '#bbbbbb',
sidebarUnreadText: '#fff',
@@ -170,9 +176,31 @@ module.exports = {
mentionColor: '#FFFFFF',
centerChannelBg: '#2F3E4E',
centerChannelColor: '#DDDDDD',
+ newMessageSeparator: '#5de5da',
linkColor: '#A4FFEB',
buttonBg: '#1dacfc',
buttonColor: '#FFFFFF'
+ },
+ windows10: {
+ type: 'Windows 10 Dark',
+ sidebarBg: '#171717',
+ sidebarText: '#eee',
+ sidebarUnreadText: '#fff',
+ sidebarTextHoverBg: '#302e30',
+ sidebarTextHoverColor: '#fff',
+ sidebarTextActiveBg: '#484748',
+ sidebarTextActiveColor: '#FFFFFF',
+ sidebarHeaderBg: '#1f1f1f',
+ sidebarHeaderTextColor: '#FFFFFF',
+ onlineIndicator: '#0177e7',
+ mentionBj: '#0177e7',
+ mentionColor: '#FFFFFF',
+ centerChannelBg: '#1F1F1F',
+ centerChannelColor: '#DDDDDD',
+ newMessageSeparator: '#CC992D',
+ linkColor: '#0177e7',
+ buttonBg: '#0177e7',
+ buttonColor: '#FFFFFF'
}
},
THEME_ELEMENTS: [
@@ -233,6 +261,10 @@ module.exports = {
uiName: 'Center Channel Text'
},
{
+ id: 'newMessageSeparator',
+ uiName: 'New message separator'
+ },
+ {
id: 'linkColor',
uiName: 'Link Color'
},
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 50438c6cf..61dcae6d8 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -4,6 +4,7 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
var AsyncClient = require('./async_client.jsx');
@@ -113,7 +114,7 @@ export function notifyMe(title, body, channel) {
if (channel) {
switchChannel(channel);
} else {
- window.location.href = '/';
+ window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
}
};
setTimeout(function closeNotificationOnTimeout() {
@@ -314,7 +315,6 @@ function getYoutubeEmbed(link) {
$('.video-type.' + youtubeId).html('Youtube - ');
$('.video-uploader.' + youtubeId).html(metadata.channelTitle);
$('.video-title.' + youtubeId).find('a').html(metadata.title);
- $('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight);
}
if (global.window.config.GoogleDeveloperKey) {
@@ -616,8 +616,9 @@ export function applyTheme(theme) {
}
if (theme.centerChannelColor) {
- changeCss('.app__content', 'color:' + theme.centerChannelColor, 2);
+ changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round', 'color:' + theme.centerChannelColor, 1);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
+ changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1);
changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1);
changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
@@ -630,8 +631,8 @@ export function applyTheme(theme) {
changeCss('.search-bar__container .search__form .search-bar', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: ' + theme.centerChannelColor, 2);
changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
- changeCss('.date-separator .separator__text, .new-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
- changeCss('.date-separator .separator__hr, .new-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
+ changeCss('.date-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.channel-intro', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
@@ -645,6 +646,11 @@ export function applyTheme(theme) {
changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2);
}
+ if (theme.newMessageSeparator) {
+ changeCss('.new-separator .separator__text', 'color:' + theme.newMessageSeparator, 1);
+ changeCss('.new-separator .separator__hr', 'border-color:' + changeOpacity(theme.newMessageSeparator, 0.5), 1);
+ }
+
if (theme.linkColor) {
changeCss('a, a:focus, a:hover', 'color:' + theme.linkColor, 1);
changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1);
diff --git a/web/sass-files/sass/partials/_loading.scss b/web/sass-files/sass/partials/_loading.scss
index d71055722..ab8601df6 100644
--- a/web/sass-files/sass/partials/_loading.scss
+++ b/web/sass-files/sass/partials/_loading.scss
@@ -37,4 +37,4 @@
to { opacity: 0.1; }
}
}
-}
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_markdown.scss b/web/sass-files/sass/partials/_markdown.scss
index de92e9d20..122586354 100644
--- a/web/sass-files/sass/partials/_markdown.scss
+++ b/web/sass-files/sass/partials/_markdown.scss
@@ -2,10 +2,20 @@
font-weight: 700;
}
.markdown__paragraph-inline {
- display: inline;
- + .markdown__paragraph-inline {
- margin-left: 4px;
- }
+ display: inline;
+ + .markdown__paragraph-inline {
+ margin-left: 4px;
+ }
+}
+.post-body {
+ hr {
+ height: 4px;
+ padding: 0;
+ margin: 15px 0 16px;
+ background-color: #e7e7e7;
+ border: 0 none;
+ @include opacity(0.2);
+ }
}
.markdown__table {
background: #fff;
@@ -21,6 +31,26 @@
}
}
}
+blockquote {
+ border: none;
+ position: relative;
+ font-size: 16px;
+ padding: 10px 10px 10px 38px;
+ margin-bottom: 0;
+ &:before {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ display: inline-block;
+ text-decoration: inherit;
+ content: "\f10d";
+ left: 8px;
+ top: 5px;
+ position: absolute;
+ font-size: 20px;
+ @include opacity(0.6);
+ }
+}
pre {
border: none;
background-color: #f7f7f7;
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index a86cb8a73..83cdde53b 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -66,8 +66,4 @@
.mention-highlight {
background-color:#fff2bb;
color: #333;
-}
-
-.mention-link {
- color:$primary-color;
} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index e362e8f7a..7532875d6 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -229,12 +229,13 @@ body.ios {
right: 0;
position: absolute;
top: 1px;
- color: #999;
+ color: #444;
+ @include opacity(0.5);
@include single-transition(all, 0.15s);
font-size: 16px;
padding: 7px 9px 6px;
&:hover, &:active {
- color: #444;
+ @include opacity(0.9);
box-shadow: none;
}
}
@@ -256,7 +257,7 @@ body.ios {
line-height: 18px;
display: inline-block;
font-size: 13px;
- color: #777;
+ @include opacity(0.6);
}
}
}
diff --git a/web/sass-files/sass/partials/_videos.scss b/web/sass-files/sass/partials/_videos.scss
index de18aa08a..9e1ce29b7 100644
--- a/web/sass-files/sass/partials/_videos.scss
+++ b/web/sass-files/sass/partials/_videos.scss
@@ -1,6 +1,7 @@
.video-div {
position:relative;
max-width: 480px;
+ margin-bottom: 8px;
.video-thumbnail {
max-width: 100%;
height: auto;
@@ -19,18 +20,15 @@
}
.video-type {
- color:grey;
+ @include opacity(0.8);
font-size:15px;
- font-weight:200;
margin:0px;
padding:0px;
}
.video-uploader {
- font-size:15px;
- margin-top:3px;
- margin-bottom:0px;
- padding:0px;
+ font-size: 13px;
+ margin: 0 0 15px;
}
.video-title {
diff --git a/web/static/images/themes/dark.png b/web/static/images/themes/mattermost dark.png
index 832f64d2e..832f64d2e 100644
--- a/web/static/images/themes/dark.png
+++ b/web/static/images/themes/mattermost dark.png
Binary files differ
diff --git a/web/static/images/themes/windows 10 dark.png b/web/static/images/themes/windows 10 dark.png
new file mode 100644
index 000000000..d65304820
--- /dev/null
+++ b/web/static/images/themes/windows 10 dark.png
Binary files differ
diff --git a/web/web.go b/web/web.go
index 1e435d47f..da7eff13d 100644
--- a/web/web.go
+++ b/web/web.go
@@ -145,8 +145,14 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
- page := NewHtmlTemplatePage("signup_team", "Signup")
- page.Render(c, w)
+ if len(c.Session.UserId) == 0 {
+ page := NewHtmlTemplatePage("signup_team", "Signup")
+ page.Render(c, w)
+ } else {
+ page := NewHtmlTemplatePage("home", "Home")
+ page.Props["TeamURL"] = c.GetTeamURL()
+ page.Render(c, w)
+ }
}
func signup(c *api.Context, w http.ResponseWriter, r *http.Request) {