summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx9
-rw-r--r--web/react/components/admin_console/email_settings.jsx2
-rw-r--r--web/react/components/admin_console/ldap_settings.jsx26
-rw-r--r--web/react/components/admin_console/rate_settings.jsx2
-rw-r--r--web/react/components/admin_console/service_settings.jsx11
-rw-r--r--web/react/components/admin_console/team_settings.jsx2
-rw-r--r--web/react/components/posts_view.jsx160
-rw-r--r--web/react/components/rhs_root_post.jsx1
-rw-r--r--web/react/components/setting_item_min.jsx2
-rw-r--r--web/react/components/settings_sidebar.jsx7
-rw-r--r--web/react/components/sidebar.jsx10
-rw-r--r--web/react/components/sidebar_right.jsx19
-rw-r--r--web/react/components/signup_team.jsx4
-rw-r--r--web/react/components/suggestion/search_channel_provider.jsx2
-rw-r--r--web/react/components/suggestion/search_user_provider.jsx2
-rw-r--r--web/react/components/team_general_tab.jsx2
-rw-r--r--web/react/components/tutorial/tutorial_tip.jsx32
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx2
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx9
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx9
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx9
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx25
-rw-r--r--web/react/components/view_image.jsx356
-rw-r--r--web/react/stores/file_store.jsx60
-rw-r--r--web/react/utils/async_client.jsx28
-rw-r--r--web/react/utils/client.jsx7
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/delayed_action.jsx27
-rw-r--r--web/react/utils/emoticons.jsx42
-rw-r--r--web/react/utils/utils.jsx14
-rw-r--r--web/sass-files/sass/partials/_base.scss4
-rw-r--r--web/sass-files/sass/partials/_content.scss2
-rw-r--r--web/sass-files/sass/partials/_files.scss5
-rw-r--r--web/sass-files/sass/partials/_modal.scss6
-rw-r--r--web/sass-files/sass/partials/_popover.scss5
-rw-r--r--web/sass-files/sass/partials/_post.scss64
-rw-r--r--web/sass-files/sass/partials/_post_right.scss4
-rw-r--r--web/sass-files/sass/partials/_responsive.scss36
-rw-r--r--web/sass-files/sass/partials/_search.scss9
-rw-r--r--web/sass-files/sass/partials/_settings.scss244
-rw-r--r--web/sass-files/sass/partials/_sidebar--right.scss6
-rw-r--r--web/sass-files/sass/partials/_tutorial.scss8
-rw-r--r--web/static/images/postArrows.pngbin0 -> 5684 bytes
-rw-r--r--web/static/js/velocity.min.js4
-rw-r--r--web/templates/head.html3
45 files changed, 829 insertions, 453 deletions
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 587fb35ed..1279f4d22 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -255,15 +255,6 @@ export default class AdminSidebar extends React.Component {
<li>
<a
href='#'
- className={this.isSelected('ldap_settings')}
- onClick={this.handleClick.bind(this, 'ldap_settings', null)}
- >
- {'LDAP Settings'}
- </a>
- </li>
- <li>
- <a
- href='#'
className={this.isSelected('legal_and_support_settings')}
onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
>
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 42e3507d6..91d73dccd 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -52,7 +52,7 @@ export default class EmailSettings extends React.Component {
var config = this.props.config;
config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked;
config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
- config.EmailSettings.SendPushlNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).checked;
+ config.EmailSettings.SendPushNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).checked;
config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked;
config.EmailSettings.FeedbackName = ReactDOM.findDOMNode(this.refs.feedbackName).value.trim();
config.EmailSettings.FeedbackEmail = ReactDOM.findDOMNode(this.refs.feedbackEmail).value.trim();
diff --git a/web/react/components/admin_console/ldap_settings.jsx b/web/react/components/admin_console/ldap_settings.jsx
index f8ea62192..6e3da2f72 100644
--- a/web/react/components/admin_console/ldap_settings.jsx
+++ b/web/react/components/admin_console/ldap_settings.jsx
@@ -92,6 +92,12 @@ export default class LdapSettings extends React.Component {
return (
<div className='wrapper--fixed'>
+ <div className='banner'>
+ <div className='banner__content'>
+ <h4 className='banner__heading'>{'Note:'}</h4>
+ <p>{'If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.'}</p>
+ </div>
+ </div>
<h3>{'LDAP Settings'}</h3>
<form
className='form-horizontal'
@@ -147,7 +153,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The domain or ip address of LDAP server.'}</p>
+ <p className='help-text'>{'The domain or IP address of LDAP server.'}</p>
</div>
</div>
<div className='form-group'>
@@ -168,7 +174,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The port to connect to the LDAP server on. Default is 389.'}</p>
+ <p className='help-text'>{'The port Mattermost will use to connect to the LDAP server. Default is 389.'}</p>
</div>
</div>
<div className='form-group'>
@@ -189,7 +195,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The base dn where mattermost should search for users.'}</p>
+ <p className='help-text'>{'The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.'}</p>
</div>
</div>
<div className='form-group'>
@@ -210,7 +216,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'Username of a user with read access to the LDAP server specified.'}</p>
+ <p className='help-text'>{'The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.'}</p>
</div>
</div>
<div className='form-group'>
@@ -231,7 +237,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'Password of the user given above.'}</p>
+ <p className='help-text'>{'Password of the user given in "Bind Username".'}</p>
</div>
</div>
<div className='form-group'>
@@ -252,7 +258,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The first name attribute of entires in the LDAP server.'}</p>
+ <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the first name of users in Mattermost.'}</p>
</div>
</div>
<div className='form-group'>
@@ -273,7 +279,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The last name attribute of entries in the LDAP server.'}</p>
+ <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.'}</p>
</div>
</div>
<div className='form-group'>
@@ -294,7 +300,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The email attribute of entries in the LDAP server.'}</p>
+ <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.'}</p>
</div>
</div>
<div className='form-group'>
@@ -315,7 +321,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The attribute of entries in the LDAP server to use for username in Mattermost. May be the same as the ID Attribute.'}</p>
+ <p className='help-text'>{'The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.'}</p>
</div>
</div>
<div className='form-group'>
@@ -336,7 +342,7 @@ export default class LdapSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
- <p className='help-text'>{'The attribute of entries in the LDAP server to use as a unique identifier. Users will use this to login. Ideally this would be the username they are used to loging in with. May be the same as the username attribute above.'}</p>
+ <p className='help-text'>{'The attribute in the LDAP server that will be used as a unique identifier in Mattermost. It should be an LDAP attribute with a value that does not change, such as username or uid. If a user’s Id Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the "LDAP Username" field on the sign in page. Normally this attribute is the same as the “Username Attribute” field above. If your team typically uses domain\\username to sign in to other services with LDAP, you may choose to put domain\\username in this field to maintain consistency between sites.'}</p>
</div>
</div>
<div className='form-group'>
diff --git a/web/react/components/admin_console/rate_settings.jsx b/web/react/components/admin_console/rate_settings.jsx
index ca9fcb074..aabb24326 100644
--- a/web/react/components/admin_console/rate_settings.jsx
+++ b/web/react/components/admin_console/rate_settings.jsx
@@ -241,7 +241,7 @@ export default class RateSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter || this.state.VaryByRemoteAddr}
/>
- <p className='help-text'>{'When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring Ngnix set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'}</p>
+ <p className='help-text'>{'When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'}</p>
</div>
</div>
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index d7582d682..e235819fe 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -172,7 +172,16 @@ export default class ServiceSettings extends React.Component {
defaultValue={this.props.config.ServiceSettings.GoogleDeveloperKey}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '}<a href='https://www.youtube.com/watch?v=Im69kzhpR3I'>{'https://www.youtube.com/watch?v=Im69kzhpR3I'}</a>{'. Leaving field blank disables the automatic generation of YouTube video previews from links.'}</p>
+ <p className='help-text'>
+ {'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '}
+ <a
+ href='https://www.youtube.com/watch?v=Im69kzhpR3I'
+ target='_blank'
+ >
+ {'https://www.youtube.com/watch?v=Im69kzhpR3I'}
+ </a>
+ {'. Leaving the field blank disables the automatic generation of YouTube video previews from links.'}
+ </p>
</div>
</div>
diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx
index 7991b9a01..9d958ce91 100644
--- a/web/react/components/admin_console/team_settings.jsx
+++ b/web/react/components/admin_console/team_settings.jsx
@@ -206,7 +206,7 @@ export default class TeamSettings extends React.Component {
defaultValue={this.props.config.TeamSettings.RestrictCreationToDomains}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Teams can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'}</p>
+ <p className='help-text'>{'Teams and user accounts can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'}</p>
</div>
</div>
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index e116fdeea..a28efbd04 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -7,6 +7,7 @@ import * as EventHelpers from '../dispatcher/event_helpers.jsx';
import * as Utils from '../utils/utils.jsx';
import Post from './post.jsx';
import Constants from '../utils/constants.jsx';
+import DelayedAction from '../utils/delayed_action.jsx';
const Preferences = Constants.Preferences;
export default class PostsView extends React.Component {
@@ -15,18 +16,26 @@ export default class PostsView extends React.Component {
this.updateState = this.updateState.bind(this);
this.handleScroll = this.handleScroll.bind(this);
+ this.handleScrollStop = this.handleScrollStop.bind(this);
this.isAtBottom = this.isAtBottom.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
this.createPosts = this.createPosts.bind(this);
this.updateScrolling = this.updateScrolling.bind(this);
this.handleResize = this.handleResize.bind(this);
+ this.scrollToBottom = this.scrollToBottom.bind(this);
this.jumpToPostNode = null;
this.wasAtBottom = true;
this.scrollHeight = 0;
- this.state = {displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false')};
+ this.scrollStopAction = new DelayedAction(this.handleScrollStop);
+
+ this.state = {
+ displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
+ isScrolling: false,
+ topPostId: null
+ };
}
static get SCROLL_TYPE_FREE() {
return 1;
@@ -69,6 +78,55 @@ export default class PostsView extends React.Component {
this.props.postViewScrolled(this.isAtBottom());
this.prevScrollHeight = this.refs.postlist.scrollHeight;
this.prevOffsetTop = this.jumpToPostNode.offsetTop;
+
+ this.updateFloatingTimestamp();
+
+ if (!this.state.isScrolling) {
+ this.setState({
+ isScrolling: true
+ });
+ }
+
+ this.scrollStopAction.fireAfter(1000);
+ }
+ handleScrollStop() {
+ this.setState({
+ isScrolling: false
+ });
+ }
+ updateFloatingTimestamp() {
+ // skip this in non-mobile view since that's when the timestamp is visible
+ if ($(window).width() > 768) {
+ return;
+ }
+
+ if (this.props.postList) {
+ // iterate through posts starting at the bottom since users are more likely to be viewing newer posts
+ for (let i = 0; i < this.props.postList.order.length; i++) {
+ const id = this.props.postList.order[i];
+ const element = ReactDOM.findDOMNode(this.refs[id]);
+
+ if (!element || element.offsetTop + element.clientHeight <= this.refs.postlist.scrollTop) {
+ // this post is off the top of the screen so the last one is at the top of the screen
+ let topPostId;
+
+ if (i > 0) {
+ topPostId = this.props.postList.order[i - 1];
+ } else {
+ // the first post we look at should always be on the screen, but handle that case anyway
+ topPostId = id;
+ }
+
+ if (topPostId !== this.state.topPostId) {
+ this.setState({
+ topPostId
+ });
+ }
+
+ break;
+ }
+ }
+ }
}
loadMorePostsTop() {
this.props.loadMorePostsTopClicked();
@@ -226,9 +284,7 @@ export default class PostsView extends React.Component {
}
updateScrolling() {
if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) {
- window.requestAnimationFrame(() => {
- this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
- });
+ this.scrollToBottom();
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_NEW_MESSAGE) {
window.requestAnimationFrame(() => {
// If separator exists scroll to it. Otherwise scroll to bottom.
@@ -278,6 +334,11 @@ export default class PostsView extends React.Component {
handleResize() {
this.updateScrolling();
}
+ scrollToBottom() {
+ window.requestAnimationFrame(() => {
+ this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
+ });
+ }
componentDidMount() {
if (this.props.postList != null) {
this.updateScrolling();
@@ -322,6 +383,12 @@ export default class PostsView extends React.Component {
if (nextState.displayNameType !== this.state.displayNameType) {
return true;
}
+ if (this.state.topPostId !== nextState.topPostId) {
+ return true;
+ }
+ if (this.state.isScrolling !== nextState.isScrolling) {
+ return true;
+ }
return false;
}
@@ -377,20 +444,36 @@ export default class PostsView extends React.Component {
}
}
+ let topPost = null;
+ if (this.state.topPostId) {
+ topPost = this.props.postList.posts[this.state.topPostId];
+ }
+
return (
- <div
- ref='postlist'
- className={'post-list-holder-by-time ' + activeClass}
- onScroll={this.handleScroll}
- >
- <div className='post-list__table'>
- <div
- ref='postlistcontent'
- className='post-list__content'
- >
- {moreMessagesTop}
- {postElements}
- {moreMessagesBottom}
+ <div className={activeClass}>
+ <FloatingTimestamp
+ isScrolling={this.state.isScrolling}
+ post={topPost}
+ />
+ <ScrollToBottomArrows
+ isScrolling={this.state.isScrolling}
+ atBottom={this.wasAtBottom}
+ onClick={this.scrollToBottom}
+ />
+ <div
+ ref='postlist'
+ className='post-list-holder-by-time'
+ onScroll={this.handleScroll}
+ >
+ <div className='post-list__table'>
+ <div
+ ref='postlistcontent'
+ className='post-list__content'
+ >
+ {moreMessagesTop}
+ {postElements}
+ {moreMessagesBottom}
+ </div>
</div>
</div>
</div>
@@ -414,3 +497,46 @@ PostsView.propTypes = {
messageSeparatorTime: React.PropTypes.number,
postsToHighlight: React.PropTypes.object
};
+
+function FloatingTimestamp({isScrolling, post}) {
+ // only show on mobile
+ if ($(window).width() > 768) {
+ return <noscript />;
+ }
+
+ if (!post) {
+ return <noscript />;
+ }
+
+ const dateString = Utils.getDateForUnixTicks(post.create_at).toDateString();
+
+ let className = 'post-list__timestamp';
+ if (isScrolling) {
+ className += ' scrolling';
+ }
+
+ return (
+ <div className={className}>
+ <span>{dateString}</span>
+ </div>
+ );
+}
+
+function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {
+ // only show on mobile
+ if ($(window).width() > 768) {
+ return <noscript />;
+ }
+
+ let className = 'post-list__arrows';
+ if (isScrolling && !atBottom) {
+ className += ' scrolling';
+ }
+
+ return (
+ <div
+ className={className}
+ onClick={onClick}
+ />
+ );
+}
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index dd9a793be..cd7f6766c 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -227,7 +227,6 @@ export default class RhsRootPost extends React.Component {
</div>
</div>
</div>
- <hr />
</div>
);
}
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 2a72be207..ffd2061fe 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -8,7 +8,7 @@ export default class SettingItemMin extends React.Component {
editButton = (
<li className='col-sm-2 section-edit'>
<a
- className='section-edit theme'
+ className='theme'
href='#'
onClick={this.props.updateSection}
>
diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx
index 4af46c35a..271ea9a41 100644
--- a/web/react/components/settings_sidebar.jsx
+++ b/web/react/components/settings_sidebar.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import * as Utils from '../utils/utils.jsx';
+
export default class SettingsSidebar extends React.Component {
componentDidUpdate() {
$('.settings-modal').find('.modal-body').scrollTop(0);
@@ -16,6 +18,11 @@ export default class SettingsSidebar extends React.Component {
this.props.updateTab(tab.name);
$(e.target).closest('.settings-modal').addClass('display--content');
}
+ componentDidMount() {
+ if (Utils.isBrowserFirefox()) {
+ $('.settings-modal .settings-table .nav').addClass('position--top');
+ }
+ }
render() {
let tabList = this.props.tabs.map(function makeTab(tab) {
let key = `${tab.name}_li`;
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index cc2279b57..18c360cb8 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -39,6 +39,7 @@ export default class Sidebar extends React.Component {
this.handleLeaveDirectChannel = this.handleLeaveDirectChannel.bind(this);
this.handleResize = this.handleResize.bind(this);
+ this.showMoreChannelsModal = this.showMoreChannelsModal.bind(this);
this.showNewChannelModal = this.showNewChannelModal.bind(this);
this.hideNewChannelModal = this.hideNewChannelModal.bind(this);
this.showMoreDirectChannelsModal = this.showMoreDirectChannelsModal.bind(this);
@@ -250,6 +251,11 @@ export default class Sidebar extends React.Component {
return a.display_name.localeCompare(b.display_name);
}
+ showMoreChannelsModal() {
+ // manually show the modal because using data-toggle messes with keyboard focus when the modal is dismissed
+ $('#more_channels').modal({'data-channeltype': 'O'}).modal('show');
+ }
+
showNewChannelModal(type) {
this.setState({newChannelModalType: type});
}
@@ -594,10 +600,8 @@ export default class Sidebar extends React.Component {
<li>
<a
href='#'
- data-toggle='modal'
className='nav-more'
- data-target='#more_channels'
- data-channeltype='O'
+ onClick={this.showMoreChannelsModal}
>
{'More...'}
</a>
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index ac1049da0..ee247265d 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -52,14 +52,29 @@ export default class SidebarRight extends React.Component {
doStrangeThings() {
// We should have a better way to do this stuff
// Hence the function name.
+ var windowWidth = $(window).outerWidth();
+ var sidebarRightWidth = $('.sidebar--right').outerWidth();
+
$('.inner__wrap').removeClass('.move--right');
$('.inner__wrap').addClass('move--left');
$('.sidebar--left').removeClass('move--right');
$('.sidebar--right').addClass('move--left');
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
-
- if (!(this.state.search_visible || this.state.post_right_visible)) {
+ if (this.state.search_visible || this.state.post_right_visible) {
+ if (windowWidth > 960) {
+ $('.inner__wrap').velocity({marginRight: sidebarRightWidth}, {duration: 500, easing: 'easeOutSine'});
+ $('.sidebar--right').velocity({translateX: 0}, {duration: 500, easing: 'easeOutSine'});
+ } else {
+ $('.inner__wrap, .sidebar--right').attr('style', '');
+ }
+ } else {
+ if (windowWidth > 960) {
+ $('.inner__wrap').velocity({marginRight: 0}, {duration: 500, easing: 'easeOutSine'});
+ $('.sidebar--right').velocity({translateX: sidebarRightWidth}, {duration: 500, easing: 'easeOutSine'});
+ } else {
+ $('.inner__wrap, .sidebar--right').attr('style', '');
+ }
$('.inner__wrap').removeClass('move--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
return (
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 0e05bc533..a554427d5 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -28,6 +28,8 @@ export default class TeamSignUp extends React.Component {
this.state = {page: 'email'};
} else if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
this.state = {page: 'gitlab'};
+ } else {
+ this.state = {page: 'none'};
}
}
@@ -119,6 +121,8 @@ export default class TeamSignUp extends React.Component {
<SSOSignupPage service={Constants.GOOGLE_SERVICE} />
</div>
);
+ } else if (this.state.page === 'none') {
+ return (<div>{'No team creation method has been enabled. Please contact an administrator for access.'}</div>);
}
}
}
diff --git a/web/react/components/suggestion/search_channel_provider.jsx b/web/react/components/suggestion/search_channel_provider.jsx
index 7547a9341..66a534907 100644
--- a/web/react/components/suggestion/search_channel_provider.jsx
+++ b/web/react/components/suggestion/search_channel_provider.jsx
@@ -19,7 +19,7 @@ class SearchChannelSuggestion extends React.Component {
onClick={onClick}
className={className}
>
- {item.name}
+ <i className='fa fa fa-plus-square'></i>{item.name}
</div>
);
}
diff --git a/web/react/components/suggestion/search_user_provider.jsx b/web/react/components/suggestion/search_user_provider.jsx
index cf2953937..0d553bfc4 100644
--- a/web/react/components/suggestion/search_user_provider.jsx
+++ b/web/react/components/suggestion/search_user_provider.jsx
@@ -22,7 +22,7 @@ class SearchUserSuggestion extends React.Component {
className='profile-img rounded'
src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at}
/>
- {item.username}
+ <i className='fa fa fa-plus-square'></i>{item.username}
</div>
);
}
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index dc615f2e8..cc06a940e 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -424,7 +424,7 @@ export default class GeneralTab extends React.Component {
</div>
</div>
</div>
- <div className='setting-list__hint'>{'Your Invite Code is used in the URL sent to people to join your team. Regenerating your Invite Code will invalidate the URLs in previous invitations, unless "Allow anyone to sign-up from login page" is enabled.'}</div>
+ <div className='setting-list__hint'>{'The Invite Code is used as part of the URL in the team invitation link created by **Get Team Invite Link** in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'}</div>
</div>
);
diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx
index d7c67cc9c..03ecdabab 100644
--- a/web/react/components/tutorial/tutorial_tip.jsx
+++ b/web/react/components/tutorial/tutorial_tip.jsx
@@ -101,22 +101,24 @@ export default class TutorialTip extends React.Component {
<div className={'tip-overlay ' + this.props.overlayClass}>
<div className='arrow'></div>
{this.props.screens[this.state.currentScreen]}
- <div className='tutorial__circles'>{dots}</div>
- <div className='text-right'>
- <button
- className='btn btn-primary'
- onClick={this.handleNext}
- >
- {buttonText}
- </button>
- <div className='tip-opt'>
- {'Seen this before? '}
- <a
- href='#'
- onClick={this.skipTutorial}
+ <div className='tutorial__footer'>
+ <div className='tutorial__circles'>{dots}</div>
+ <div className='text-right'>
+ <button
+ className='btn btn-primary'
+ onClick={this.handleNext}
>
- {'Opt out of these tips.'}
- </a>
+ {buttonText}
+ </button>
+ <div className='tip-opt'>
+ {'Seen this before? '}
+ <a
+ href='#'
+ onClick={this.skipTutorial}
+ >
+ {'Opt out of these tips.'}
+ </a>
+ </div>
</div>
</div>
</div>
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
index b7d90922a..8ec3863f3 100644
--- a/web/react/components/user_settings/custom_theme_chooser.jsx
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -104,7 +104,7 @@ export default class CustomThemeChooser extends React.Component {
>
<label className='custom-label'>{element.uiName}</label>
<div
- className='input-group theme-group dropdown'
+ className='input-group theme-group group--code dropdown'
id={element.id}
>
<select
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index 9ebb55646..1506e3c98 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -162,7 +162,14 @@ export default class ManageIncomingHooks extends React.Component {
return (
<div key='addIncomingHook'>
- {'Create webhook URLs for use in external integrations. Please see '}<a href='http://mattermost.org/webhooks'>{'http://mattermost.org/webhooks'}</a> {' to learn more.'}
+ {'Create webhook URLs for use in external integrations. Please see '}
+ <a
+ href='http://mattermost.org/webhooks'
+ target='_blank'
+ >
+ {'http://mattermost.org/webhooks'}
+ </a>
+ {' to learn more.'}
<div><label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label></div>
<div className='row padding-top'>
<div className='col-sm-10 padding-bottom'>
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index ede639691..17acf0f10 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -240,7 +240,14 @@ export default class ManageOutgoingHooks extends React.Component {
return (
<div key='addOutgoingHook'>
- {'Create webhooks to send new message events to an external integration. Please see '}<a href='http://mattermost.org/webhooks'>{'http://mattermost.org/webhooks'}</a> {' to learn more.'}
+ {'Create webhooks to send new message events to an external integration. Please see '}
+ <a
+ href='http://mattermost.org/webhooks'
+ target='_blank'
+ >
+ {'http://mattermost.org/webhooks'}
+ </a>
+ {' to learn more.'}
<div><label className='control-label padding-top x2'>{'Add a new outgoing webhook'}</label></div>
<div className='padding-top divider-light'></div>
<div className='padding-top'>
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index ad41ab771..7bfc9fdbd 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -74,6 +74,10 @@ export default class UserSettingsAppearance extends React.Component {
this.props.setEnforceFocus(true);
}
+ scrollToTop() {
+ $('.ps-container.modal-body').scrollTop(0);
+ $('.ps-container.modal-body').perfectScrollbar('update');
+ }
submitTheme(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
@@ -88,9 +92,7 @@ export default class UserSettingsAppearance extends React.Component {
this.props.setRequireConfirm(false);
this.originalTheme = Object.assign({}, this.state.theme);
-
- $('.ps-container.modal-body').scrollTop(0);
- $('.ps-container.modal-body').perfectScrollbar('update');
+ this.scrollToTop();
},
(err) => {
var state = this.getStateFromStores();
@@ -129,6 +131,7 @@ export default class UserSettingsAppearance extends React.Component {
const state = this.getStateFromStores();
state.serverError = null;
this.setState(state);
+ this.scrollToTop();
Utils.applyTheme(state.theme);
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index c464258de..96c3985d0 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -141,6 +141,9 @@ export default class UserSettingsDisplay extends React.Component {
);
}
+ const showUsername = 'Show username (team default)';
+ const showNickname = 'Show nickname if one exists, otherwise show first and last name';
+ const showFullName = 'Show first and last name';
if (this.props.activeSection === 'name_format') {
const nameFormat = [false, false, false];
if (this.state.nameFormat === 'nickname_full_name') {
@@ -157,10 +160,10 @@ export default class UserSettingsDisplay extends React.Component {
<label>
<input
type='radio'
- checked={nameFormat[0]}
- onChange={this.handleNameRadio.bind(this, 'nickname_full_name')}
+ checked={nameFormat[1]}
+ onChange={this.handleNameRadio.bind(this, 'username')}
/>
- {'Show nickname if one exists, otherwise show first and last name (team default)'}
+ {showUsername}
</label>
<br/>
</div>
@@ -168,10 +171,10 @@ export default class UserSettingsDisplay extends React.Component {
<label>
<input
type='radio'
- checked={nameFormat[1]}
- onChange={this.handleNameRadio.bind(this, 'username')}
+ checked={nameFormat[0]}
+ onChange={this.handleNameRadio.bind(this, 'nickname_full_name')}
/>
- {'Show username'}
+ {showNickname}
</label>
<br/>
</div>
@@ -182,11 +185,11 @@ export default class UserSettingsDisplay extends React.Component {
checked={nameFormat[2]}
onChange={this.handleNameRadio.bind(this, 'full_name')}
/>
- {'Show first and last name'}
+ {showFullName}
</label>
<br/>
</div>
- <div><br/>{'Set what name to display in the Direct Messages list.'}</div>
+ <div><br/>{'Set how to display other user\'s names in posts and the Direct Messages list.'}</div>
</div>
];
@@ -205,11 +208,11 @@ export default class UserSettingsDisplay extends React.Component {
} else {
let describe = '';
if (this.state.nameFormat === 'username') {
- describe = 'Show username';
+ describe = showUsername;
} else if (this.state.nameFormat === 'full_name') {
- describe = 'Show first and last name';
+ describe = showFullName;
} else {
- describe = 'Show nickname if one exists, otherwise show first and last name (team default)';
+ describe = showNickname;
}
nameFormatSection = (
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 820f8fd8e..7edf6283b 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -1,9 +1,11 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import * as AsyncClient from '../utils/async_client.jsx';
import * as Client from '../utils/client.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
+import FileStore from '../stores/file_store.jsx';
import ViewImagePopoverBar from './view_image_popover_bar.jsx';
const Modal = ReactBootstrap.Modal;
const KeyCodes = Constants.KeyCodes;
@@ -12,80 +14,90 @@ export default class ViewImageModal extends React.Component {
constructor(props) {
super(props);
- this.canSetState = false;
-
+ this.showImage = this.showImage.bind(this);
this.loadImage = this.loadImage.bind(this);
+
this.handleNext = this.handleNext.bind(this);
this.handlePrev = this.handlePrev.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
- this.getPublicLink = this.getPublicLink.bind(this);
- this.getPreviewImagePath = this.getPreviewImagePath.bind(this);
+
this.onModalShown = this.onModalShown.bind(this);
this.onModalHidden = this.onModalHidden.bind(this);
+
+ this.onFileStoreChange = this.onFileStoreChange.bind(this);
+
+ this.getPublicLink = this.getPublicLink.bind(this);
+ this.getPreviewImagePath = this.getPreviewImagePath.bind(this);
this.onMouseEnterImage = this.onMouseEnterImage.bind(this);
this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this);
- var loaded = [];
- var progress = [];
+ const loaded = [];
+ const progress = [];
for (var i = 0; i < this.props.filenames.length; i++) {
loaded.push(false);
progress.push(0);
}
+
this.state = {
imgId: this.props.startId,
+ fileInfo: new Map(),
imgHeight: '100%',
- loaded: loaded,
- progress: progress,
- images: {},
- fileSizes: {},
- fileMimes: {},
- showFooter: false,
- isPlaying: {},
- isLoading: {}
+ loaded,
+ progress,
+ showFooter: false
};
}
+
handleNext(e) {
if (e) {
e.stopPropagation();
}
- var id = this.state.imgId + 1;
+ let id = this.state.imgId + 1;
if (id > this.props.filenames.length - 1) {
id = 0;
}
- this.setState({imgId: id});
- this.loadImage(id);
+ this.showImage(id);
}
+
handlePrev(e) {
if (e) {
e.stopPropagation();
}
- var id = this.state.imgId - 1;
+ let id = this.state.imgId - 1;
if (id < 0) {
id = this.props.filenames.length - 1;
}
- this.setState({imgId: id});
- this.loadImage(id);
+ this.showImage(id);
}
+
handleKeyPress(e) {
- if (!e || !this.props.show) {
- return;
- } else if (e.keyCode === KeyCodes.RIGHT) {
+ if (e.keyCode === KeyCodes.RIGHT) {
this.handleNext();
} else if (e.keyCode === KeyCodes.LEFT) {
this.handlePrev();
}
}
+
onModalShown(nextProps) {
- this.setState({imgId: nextProps.startId});
- this.loadImage(nextProps.startId);
+ $(window).on('keyup', this.handleKeyPress);
+
+ this.showImage(nextProps.startId);
+
+ FileStore.addChangeListener(this.onFileStoreChange);
}
+
onModalHidden() {
+ $(window).off('keyup', this.handleKeyPress);
+
if (this.refs.video) {
var video = ReactDOM.findDOMNode(this.refs.video);
video.pause();
video.currentTime = 0;
}
+
+ FileStore.removeChangeListener(this.onFileStoreChange);
}
+
componentWillReceiveProps(nextProps) {
if (nextProps.show === true && this.props.show === false) {
this.onModalShown(nextProps);
@@ -93,31 +105,65 @@ export default class ViewImageModal extends React.Component {
this.onModalHidden();
}
}
- loadImage(id) {
- var imgHeight = $(window).height() - 100;
+
+ onFileStoreChange(filename) {
+ const id = this.props.filenames.indexOf(filename);
+
+ if (id !== -1 && !this.state.loaded[id]) {
+ const fileInfo = this.state.fileInfo;
+ fileInfo.set(filename, FileStore.getInfo(filename));
+ this.setState({fileInfo});
+
+ this.loadImage(id, filename);
+ }
+ }
+
+ showImage(id) {
+ this.setState({imgId: id});
+
+ const imgHeight = $(window).height() - 100;
this.setState({imgHeight});
- var filename = this.props.filenames[id];
+ const filename = this.props.filenames[id];
- var fileInfo = Utils.splitFileLocation(filename);
- var fileType = Utils.getFileType(fileInfo.ext);
+ if (!FileStore.hasInfo(filename)) {
+ // the image will actually be loaded once we know what we need to load
+ AsyncClient.getFileInfo(filename);
+ return;
+ }
+
+ if (!this.state.loaded[id]) {
+ this.loadImage(id, filename);
+ }
+ }
+
+ loadImage(id, filename) {
+ const fileInfo = FileStore.getInfo(filename);
+ const fileType = Utils.getFileType(fileInfo.extension);
if (fileType === 'image') {
- var img = new Image();
- img.load(this.getPreviewImagePath(filename),
- () => {
- const progress = this.state.progress;
- progress[id] = img.completedPercentage;
- this.setState({progress});
- });
+ let previewUrl;
+ if (fileInfo.has_image_preview) {
+ previewUrl = fileInfo.getPreviewImagePath(filename);
+ } else {
+ // some images (eg animated gifs) just show the file itself and not a preview
+ previewUrl = Utils.getFileUrl(filename);
+ }
+
+ const img = new Image();
+ img.load(
+ previewUrl,
+ () => {
+ const progress = this.state.progress;
+ progress[id] = img.completedPercentage;
+ this.setState({progress});
+ }
+ );
img.onload = () => {
const loaded = this.state.loaded;
loaded[id] = true;
this.setState({loaded});
};
- var images = this.state.images;
- images[id] = img;
- this.setState({images});
} else {
// there's nothing to load for non-image files
var loaded = this.state.loaded;
@@ -125,169 +171,82 @@ export default class ViewImageModal extends React.Component {
this.setState({loaded});
}
}
- playGif(e, filename, fileUrl) {
- var isLoading = this.state.isLoading;
- var isPlaying = this.state.isPlaying;
-
- isLoading[filename] = fileUrl;
- this.setState({isLoading});
-
- var img = new Image();
- img.load(fileUrl);
- img.onload = () => {
- delete isLoading[filename];
- isPlaying[filename] = fileUrl;
- this.setState({isPlaying, isLoading});
- };
- img.onError = () => {
- delete isLoading[filename];
- this.setState({isLoading});
- };
-
- e.stopPropagation();
- e.preventDefault();
- }
- stopGif(e, filename) {
- var isPlaying = this.state.isPlaying;
- delete isPlaying[filename];
- this.setState({isPlaying});
-
- e.stopPropagation();
- e.preventDefault();
- }
- componentDidMount() {
- $(window).on('keyup', this.handleKeyPress);
- // keep track of whether or not this component is mounted so we can safely set the state asynchronously
- this.canSetState = true;
- }
- componentWillUnmount() {
- this.canSetState = false;
- $(window).off('keyup', this.handleKeyPress);
- }
getPublicLink() {
var data = {};
data.channel_id = this.props.channelId;
data.user_id = this.props.userId;
data.filename = this.props.filenames[this.state.imgId];
- Client.getPublicLink(data,
- function sucess(serverData) {
+ Client.getPublicLink(
+ data,
+ (serverData) => {
if (Utils.isMobile()) {
window.location.href = serverData.public_link;
} else {
window.open(serverData.public_link);
}
},
- function error() {}
+ () => {}
);
}
+
getPreviewImagePath(filename) {
// Returns the path to a preview image that can be used to represent a file.
var fileInfo = Utils.splitFileLocation(filename);
var fileType = Utils.getFileType(fileInfo.ext);
if (fileType === 'image') {
- if (filename in this.state.isPlaying) {
- return this.state.isPlaying[filename];
- }
-
// This is a temporary patch to fix issue with old files using absolute paths
if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) {
fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1];
}
fileInfo.path = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
- return fileInfo.path + '_preview.jpg' + '?' + Utils.getSessionIndex();
+ return fileInfo.path + '_preview.jpg?' + Utils.getSessionIndex();
}
// only images have proper previews, so just use a placeholder icon for non-images
return Utils.getPreviewImagePathForFileType(fileType);
}
+
onMouseEnterImage() {
this.setState({showFooter: true});
}
+
onMouseLeaveImage() {
this.setState({showFooter: false});
}
+
render() {
if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) {
return <div/>;
}
- var filename = this.props.filenames[this.state.imgId];
- var fileUrl = Utils.getFileUrl(filename);
-
- var name = decodeURIComponent(Utils.getFileName(filename));
+ const filename = this.props.filenames[this.state.imgId];
+ const fileUrl = Utils.getFileUrl(filename);
var content;
- var bgClass = '';
if (this.state.loaded[this.state.imgId]) {
- var fileInfo = Utils.splitFileLocation(filename);
- var fileType = Utils.getFileType(fileInfo.ext);
+ // if a file has been loaded, we also have its info
+ const fileInfo = this.state.fileInfo.get(filename);
- if (fileType === 'image') {
- if (!(filename in this.state.fileMimes)) {
- Client.getFileInfo(
- filename,
- (data) => {
- if (this.canSetState) {
- var fileMimes = this.state.fileMimes;
- fileMimes[filename] = data.mime;
- this.setState(fileMimes);
- }
- },
- () => {}
- );
- }
+ const extension = Utils.splitFileLocation(filename).ext;
+ const fileType = Utils.getFileType(extension);
- var playbackControls = '';
- if (this.state.fileMimes[filename] === 'image/gif' && !(filename in this.state.isLoading)) {
- if (filename in this.state.isPlaying) {
- playbackControls = (
- <div
- className='file-playback-controls stop'
- onClick={(e) => this.stopGif(e, filename)}
- >
- {"■"}
- </div>
- );
- } else {
- playbackControls = (
- <div
- className='file-playback-controls play'
- onClick={(e) => this.playGif(e, filename, fileUrl)}
- >
- {"►"}
- </div>
- );
- }
- }
-
- var loadingIndicator = '';
- if (this.state.isLoading[filename] === fileUrl) {
- loadingIndicator = (
- <img
- className='spinner file__loading'
- src='/static/images/load.gif'
- />
- );
- playbackControls = '';
+ if (fileType === 'image') {
+ let previewUrl;
+ if (fileInfo.has_preview_image) {
+ previewUrl = this.getPreviewImagePath(filename);
+ } else {
+ previewUrl = fileUrl;
}
- // image files just show a preview of the file
content = (
- <a
- href={fileUrl}
- target='_blank'
- >
- {loadingIndicator}
- {playbackControls}
- <img
- style={{maxHeight: this.state.imgHeight}}
- ref='image'
- src={this.getPreviewImagePath(filename)}
- />
- </a>
+ <ImagePreview
+ fileUrl={fileUrl}
+ previewUrl={previewUrl}
+ maxHeight={this.state.imgHeight}
+ />
);
} else if (fileType === 'video' || fileType === 'audio') {
let width = Constants.WEB_VIDEO_WIDTH;
@@ -311,11 +270,13 @@ export default class ViewImageModal extends React.Component {
);
} else {
// non-image files include a section providing details about the file
- var infoString = 'File type ' + fileInfo.ext.toUpperCase();
- if (this.state.fileSizes[filename] && this.state.fileSizes[filename] >= 0) {
- infoString += ', Size ' + Utils.fileSizeToString(this.state.fileSizes[filename]);
+ let infoString = 'File type ' + fileInfo.extension.toUpperCase();
+ if (fileInfo.size > 0) {
+ infoString += ', Size ' + Utils.fileSizeToString(fileInfo.size);
}
+ const name = decodeURIComponent(Utils.getFileName(filename));
+
content = (
<div className='file-details__container'>
<a
@@ -335,53 +296,16 @@ export default class ViewImageModal extends React.Component {
</div>
</div>
);
- bgClass = 'white-bg';
-
- // asynchronously request the actual size of this file
- if (!(filename in this.state.fileSizes)) {
- Client.getFileInfo(
- filename,
- function success(data) {
- if (this.canSetState) {
- var fileSizes = this.state.fileSizes;
- fileSizes[filename] = parseInt(data.size, 10);
- this.setState(fileSizes);
- }
- }.bind(this),
- function fail() {}
- );
- }
}
} else {
// display a progress indicator when the preview for an image is still loading
- var percentage = Math.floor(this.state.progress[this.state.imgId]);
- if (percentage) {
- content = (
- <div>
- <img
- className='loader-image'
- src='/static/images/load.gif'
- />
- <span className='loader-percent'>
- {'Previewing ' + percentage + '%'}
- </span>
- </div>
- );
- } else {
- content = (
- <div>
- <img
- className='loader-image'
- src='/static/images/load.gif'
- />
- </div>
- );
- }
- bgClass = 'black-bg';
+ const progress = Math.floor(this.state.progress[this.state.imgId]);
+
+ content = <LoadingImagePreview progress={progress} />;
}
- var leftArrow = '';
- var rightArrow = '';
+ let leftArrow = null;
+ let rightArrow = null;
if (this.props.filenames.length > 1) {
leftArrow = (
<a
@@ -427,7 +351,6 @@ export default class ViewImageModal extends React.Component {
onClick={this.props.onModalDismissed}
>
<div
- className={bgClass}
onMouseEnter={this.onMouseEnterImage}
onMouseLeave={this.onMouseLeaveImage}
onClick={(e) => e.stopPropagation()}
@@ -471,3 +394,38 @@ ViewImageModal.propTypes = {
userId: React.PropTypes.string,
startId: React.PropTypes.number
};
+
+function LoadingImagePreview({progress}) {
+ let progressView = null;
+ if (progress) {
+ progressView = (
+ <span className='loader-percent'>
+ {'Loading ' + progress + '%'}
+ </span>
+ );
+ }
+
+ return (
+ <div className='view-image__loading'>
+ <img
+ className='loader-image'
+ src='/static/images/load.gif'
+ />
+ {progressView}
+ </div>
+ );
+}
+
+function ImagePreview({maxHeight, fileUrl, previewUrl}) {
+ return (
+ <a
+ href={fileUrl}
+ target='_blank'
+ >
+ <img
+ style={{maxHeight}}
+ src={previewUrl}
+ />
+ </a>
+ );
+}
diff --git a/web/react/stores/file_store.jsx b/web/react/stores/file_store.jsx
new file mode 100644
index 000000000..ca8c6a96b
--- /dev/null
+++ b/web/react/stores/file_store.jsx
@@ -0,0 +1,60 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
+import Constants from '../utils/constants.jsx';
+import EventEmitter from 'events';
+
+const ActionTypes = Constants.ActionTypes;
+
+const CHANGE_EVENT = 'changed';
+
+class FileStore extends EventEmitter {
+ constructor() {
+ super();
+
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.emitChange = this.emitChange.bind(this);
+
+ this.handleEventPayload = this.handleEventPayload.bind(this);
+ this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
+
+ this.fileInfo = new Map();
+ }
+
+ addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ }
+ removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+ emitChange(filename) {
+ this.emit(CHANGE_EVENT, filename);
+ }
+
+ hasInfo(filename) {
+ return this.fileInfo.has(filename);
+ }
+
+ getInfo(filename) {
+ return this.fileInfo.get(filename);
+ }
+
+ setInfo(filename, info) {
+ this.fileInfo.set(filename, info);
+ }
+
+ handleEventPayload(payload) {
+ const action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.RECIEVED_FILE_INFO:
+ this.setInfo(action.filename, action.info);
+ this.emitChange(action.filename);
+ break;
+ }
+ }
+}
+
+export default new FileStore();
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 88b5aa739..f218270da 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -769,3 +769,31 @@ export function getSuggestedCommands(command, suggestionId, component) {
}
);
}
+
+export function getFileInfo(filename) {
+ const callName = 'getFileInfo' + filename;
+
+ if (isCallInProgress(callName)) {
+ return;
+ }
+
+ callTracker[callName] = utils.getTimestamp();
+
+ client.getFileInfo(
+ filename,
+ (data) => {
+ callTracker[callName] = 0;
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_FILE_INFO,
+ filename,
+ info: data
+ });
+ },
+ (err) => {
+ callTracker[callName] = 0;
+
+ dispatchError(err, 'getFileInfo');
+ }
+ );
+}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 2a90da168..e1c331aff 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -280,6 +280,7 @@ export function loginByEmail(name, email, password, success, error) {
data: JSON.stringify({name, email, password}),
success: function onSuccess(data, textStatus, xhr) {
track('api', 'api_users_login_success', data.team_id, 'email', data.email);
+ sessionStorage.removeItem(data.id + '_last_error');
BrowserStore.signalLogin();
success(data, textStatus, xhr);
},
@@ -301,6 +302,8 @@ export function loginByLdap(teamName, id, password, success, error) {
data: JSON.stringify({teamName, id, password}),
success: function onSuccess(data, textStatus, xhr) {
track('api', 'api_users_loginLdap_success', data.team_id, 'id', id);
+ sessionStorage.removeItem(data.id + '_last_error');
+ BrowserStore.signalLogin();
success(data, textStatus, xhr);
},
error: function onError(xhr, status, err) {
@@ -1110,7 +1113,9 @@ export function getFileInfo(filename, success, error) {
dataType: 'json',
contentType: 'application/json',
type: 'GET',
- success,
+ success: (data) => {
+ success(data);
+ },
error: function onError(xhr, status, err) {
var e = handleError('getFileInfo', xhr, status, err);
error(e);
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index fa5fa8e8d..0298ce533 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -37,6 +37,7 @@ export default {
RECIEVED_STATUSES: null,
RECIEVED_PREFERENCE: null,
RECIEVED_PREFERENCES: null,
+ RECIEVED_FILE_INFO: null,
RECIEVED_MSG: null,
diff --git a/web/react/utils/delayed_action.jsx b/web/react/utils/delayed_action.jsx
new file mode 100644
index 000000000..4f6239ad0
--- /dev/null
+++ b/web/react/utils/delayed_action.jsx
@@ -0,0 +1,27 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class DelayedAction {
+ constructor(action) {
+ this.action = action;
+
+ this.timer = -1;
+
+ // bind fire since it doesn't get passed the correct this value with setTimeout
+ this.fire = this.fire.bind(this);
+ }
+
+ fire() {
+ this.action();
+
+ this.timer = -1;
+ }
+
+ fireAfter(timeout) {
+ if (this.timer >= 0) {
+ window.clearTimeout(this.timer);
+ }
+
+ this.timer = window.setTimeout(this.fire, timeout);
+ }
+}
diff --git a/web/react/utils/emoticons.jsx b/web/react/utils/emoticons.jsx
index fa5177232..23a847969 100644
--- a/web/react/utils/emoticons.jsx
+++ b/web/react/utils/emoticons.jsx
@@ -2,21 +2,21 @@
// See License.txt for license information.
const emoticonPatterns = {
- smile: /(^|\s)(:-?\))(?=$|\s)/g, // :)
+ slightly_smiling_face: /(^|\s)(:-?\))(?=$|\s)/g, // :)
wink: /(^|\s)(;-?\))(?=$|\s)/g, // ;)
open_mouth: /(^|\s)(:o)(?=$|\s)/gi, // :o
scream: /(^|\s)(:-o)(?=$|\s)/gi, // :-o
smirk: /(^|\s)(:-?])(?=$|\s)/g, // :]
- grinning: /(^|\s)(:-?d)(?=$|\s)/gi, // :D
+ smile: /(^|\s)(:-?d)(?=$|\s)/gi, // :D
stuck_out_tongue_closed_eyes: /(^|\s)(x-d)(?=$|\s)/gi, // x-d
stuck_out_tongue: /(^|\s)(:-?p)(?=$|\s)/gi, // :p
rage: /(^|\s)(:-?[\[@])(?=$|\s)/g, // :@
- frowning: /(^|\s)(:-?\()(?=$|\s)/g, // :(
- sob: /(^|\s)(:['’]-?\(|:&#x27;\(|:&#39;\()(?=$|\s)/g, // :`(
- pensive: /(^|\s)(:-?\/)(?=$|\s)/g, // :/
+ slightly_frowning_face: /(^|\s)(:-?\()(?=$|\s)/g, // :(
+ cry: /(^|\s)(:['’]-?\(|:&#x27;\(|:&#39;\()(?=$|\s)/g, // :`(
+ confused: /(^|\s)(:-?\/)(?=$|\s)/g, // :/
confounded: /(^|\s)(:-?s)(?=$|\s)/gi, // :s
- flushed: /(^|\s)(:-?\|)(?=$|\s)/g, // :|
- relaxed: /(^|\s)(:-?\$)(?=$|\s)/g, // :$
+ neutral_face: /(^|\s)(:-?\|)(?=$|\s)/g, // :|
+ flushed: /(^|\s)(:-?\$)(?=$|\s)/g, // :$
mask: /(^|\s)(:-x)(?=$|\s)/gi, // :-x
heart: /(^|\s)(<3|&lt;3)(?=$|\s)/g, // <3
broken_heart: /(^|\s)(<\/3|&lt;&#x2F;3)(?=$|\s)/g, // </3
@@ -34,10 +34,10 @@ function initializeEmoticonMap() {
'baby_bottle,baby_chick,baby_symbol,back,baggage_claim,balloon,ballot_box_with_check,bamboo,banana,bangbang,' +
'bank,bar_chart,barber,baseball,basketball,bath,bathtub,battery,bear,bee,beer,beers,beetle,beginner,bell,bento,' +
'bicyclist,bike,bikini,bird,birthday,black_circle,black_joker,black_medium_small_square,black_medium_square,' +
- 'black_nib,black_small_square,black_square,black_square_button,blossom,blowfish,blue_book,blue_car,blue_heart,' +
- 'blush,boar,boat,bomb,book,bookmark,bookmark_tabs,books,boom,boot,bouquet,bow,bowling,bowtie,boy,bread,' +
- 'bride_with_veil,bridge_at_night,briefcase,broken_heart,bug,bulb,bullettrain_front,bullettrain_side,bus,busstop,' +
- 'bust_in_silhouette,busts_in_silhouette,cactus,cake,calendar,calling,camel,camera,cancer,candy,capital_abcd,' +
+ 'black_large_square,black_nib,black_small_square,black_square,black_square_button,blossom,blowfish,blue_book,' +
+ 'blue_car,blue_heart,blush,boar,boat,bomb,book,bookmark,bookmark_tabs,books,boom,boot,bouquet,bow,bowling,bowtie,' +
+ 'boy,bread,bride_with_veil,bridge_at_night,briefcase,broken_heart,bug,bulb,bullettrain_front,bullettrain_side,bus,' +
+ 'busstop,bust_in_silhouette,busts_in_silhouette,cactus,cake,calendar,calling,camel,camera,cancer,candy,capital_abcd,' +
'capricorn,car,card_index,carousel_horse,cat,cat2,cd,chart,chart_with_downwards_trend,chart_with_upwards_trend,' +
'checkered_flag,cherries,cherry_blossom,chestnut,chicken,children_crossing,chocolate_bar,christmas_tree,church,' +
'cinema,circus_tent,city_sunrise,city_sunset,cl,clap,clapper,clipboard,clock1,clock10,clock1030,clock11,' +
@@ -92,16 +92,16 @@ function initializeEmoticonMap() {
'rugby_football,runner,running,running_shirt_with_sash,sa,sagittarius,sailboat,sake,sandal,santa,satellite,' +
'satisfied,saxophone,school,school_satchel,scissors,scorpius,scream,scream_cat,scroll,seat,secret,see_no_evil,' +
'seedling,seven,shaved_ice,sheep,shell,ship,shipit,shirt,shit,shoe,shower,signal_strength,six,six_pointed_star,' +
- 'ski,skull,sleeping,sleepy,slot_machine,small_blue_diamond,small_orange_diamond,small_red_triangle,' +
- 'small_red_triangle_down,smile,smile_cat,smiley,smiley_cat,smiling_imp,smirk,smirk_cat,smoking,snail,snake,' +
- 'snowboarder,snowflake,snowman,sob,soccer,soon,sos,sound,space_invader,spades,spaghetti,sparkle,sparkler,' +
- 'sparkles,sparkling_heart,speak_no_evil,speaker,speech_balloon,speedboat,squirrel,star,star2,stars,station,' +
- 'statue_of_liberty,steam_locomotive,stew,straight_ruler,strawberry,stuck_out_tongue,stuck_out_tongue_closed_eyes,' +
- 'stuck_out_tongue_winking_eye,sun_with_face,sunflower,sunglasses,sunny,sunrise,sunrise_over_mountains,surfer,' +
- 'sushi,suspect,suspension_railway,sweat,sweat_drops,sweat_smile,sweet_potato,swimmer,symbols,syringe,tada,' +
- 'tanabata_tree,tangerine,taurus,taxi,tea,telephone,telephone_receiver,telescope,tennis,tent,thought_balloon,' +
- 'three,thumbsdown,thumbsup,ticket,tiger,tiger2,tired_face,tm,toilet,tokyo_tower,tomato,tongue,top,tophat,' +
- 'tractor,traffic_light,train,train2,tram,triangular_flag_on_post,triangular_ruler,trident,triumph,trolleybus,' +
+ 'ski,skull,sleeping,sleepy,slightly_smiling_face,slightly_frowning_face,slot_machine,small_blue_diamond,' +
+ 'small_orange_diamond,small_red_triangle,small_red_triangle_down,smile,smile_cat,smiley,smiley_cat,smiling_imp,' +
+ 'smirk,smirk_cat,smoking,snail,snake,snowboarder,snowflake,snowman,sob,soccer,soon,sos,sound,space_invader,spades,' +
+ 'spaghetti,sparkle,sparkler,sparkles,sparkling_heart,speak_no_evil,speaker,speech_balloon,speedboat,squirrel,star,' +
+ 'star2,stars,station,statue_of_liberty,steam_locomotive,stew,straight_ruler,strawberry,stuck_out_tongue,' +
+ 'stuck_out_tongue_closed_eyes,stuck_out_tongue_winking_eye,sun_with_face,sunflower,sunglasses,sunny,sunrise,' +
+ 'sunrise_over_mountains,surfer,sushi,suspect,suspension_railway,sweat,sweat_drops,sweat_smile,sweet_potato,swimmer,' +
+ 'symbols,syringe,tada,tanabata_tree,tangerine,taurus,taxi,tea,telephone,telephone_receiver,telescope,tennis,tent,' +
+ 'thought_balloon,three,thumbsdown,thumbsup,ticket,tiger,tiger2,tired_face,tm,toilet,tokyo_tower,tomato,tongue,top,' +
+ 'tophat,tractor,traffic_light,train,train2,tram,triangular_flag_on_post,triangular_ruler,trident,triumph,trolleybus,' +
'trollface,trophy,tropical_drink,tropical_fish,truck,trumpet,tshirt,tulip,turtle,tv,twisted_rightwards_arrows,' +
'two,two_hearts,two_men_holding_hands,two_women_holding_hands,u5272,u5408,u55b6,u6307,u6708,u6709,u6e80,u7121,' +
'u7533,u7981,u7a7a,uk,umbrella,unamused,underage,unlock,up,us,v,vertical_traffic_light,vhs,vibration_mode,' +
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 2ade00d29..24d27b10a 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -567,7 +567,7 @@ export function applyTheme(theme) {
}
if (theme.sidebarHeaderBg) {
- changeCss('.sidebar--left .team__header, .sidebar--menu .team__header', 'background:' + theme.sidebarHeaderBg, 1);
+ changeCss('.sidebar--left .team__header, .sidebar--menu .team__header, .post-list__timestamp', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarHeaderBg, 1);
@@ -575,7 +575,7 @@ export function applyTheme(theme) {
}
if (theme.sidebarHeaderTextColor) {
- changeCss('.sidebar--left .team__header .header__info, .sidebar--menu .team__header .header__info', 'color:' + theme.sidebarHeaderTextColor, 1);
+ changeCss('.sidebar--left .team__header .header__info, .sidebar--menu .team__header .header__info, .post-list__timestamp', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('.sidebar--left .team__header .navbar-right .dropdown__icon, .sidebar--menu .team__header .navbar-right .dropdown__icon', 'fill:' + theme.sidebarHeaderTextColor, 1);
changeCss('.sidebar--left .team__header .user__name, .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8), 1);
changeCss('.sidebar--left .team__header:hover .user__name, .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor, 1);
@@ -617,6 +617,7 @@ export function applyTheme(theme) {
if (theme.centerChannelColor) {
changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name, .tip-overlay', 'color:' + theme.centerChannelColor, 1);
+ changeCss('#archive-link-home', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
changeCss('.mentions--top, .suggestion-list', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
changeCss('.mentions--top, .suggestion-list', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
@@ -647,10 +648,11 @@ export function applyTheme(theme) {
changeCss('.attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro .channel-intro__content, .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
- changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.search-item-container, .post-right__container .post.post--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
changeCss('.modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro, .settings-modal .settings-table .settings-content .divider-dark, hr, .settings-modal .settings-table .settings-links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.post.current--user .post__body, .post.post--comment.other--root.current--user .post-comment, pre', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.post.current--user .post__body, .post.post--comment.other--root.current--user .post-comment, pre, .post-right__container .post.post--root', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.post.current--user .post__body, .post.post--comment.other--root.current--user .post-comment, .post.same--root.post--comment .post__body, .modal .more-table tbody>tr td, .member-div:first-child, .member-div, .access-history__table .access__report, .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2);
changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('.post:hover, .modal .more-table tbody>tr:hover td, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
@@ -684,11 +686,11 @@ export function applyTheme(theme) {
}
if (theme.mentionHighlightBg) {
- changeCss('.mention-highlight, .search-highlight, #archive-link-home', 'background:' + theme.mentionHighlightBg, 1);
+ changeCss('.mention-highlight, .search-highlight', 'background:' + theme.mentionHighlightBg, 1);
}
if (theme.mentionHighlightBg) {
- changeCss('.post.post--highlight, #archive-link-home', 'background:' + changeOpacity(theme.mentionHighlightBg, 0.5), 1);
+ changeCss('.post.post--highlight', 'background:' + changeOpacity(theme.mentionHighlightBg, 0.5), 1);
}
if (theme.mentionHighlightLink) {
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 4f9e1d5c7..04ba9c51e 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -125,10 +125,6 @@ a:focus, a:hover {
}
}
-select {
- -moz-appearance:none;
-}
-
.form-control {
@include border-radius(2px);
&:focus {
diff --git a/web/sass-files/sass/partials/_content.scss b/web/sass-files/sass/partials/_content.scss
index da75bc61b..b54c97b41 100644
--- a/web/sass-files/sass/partials/_content.scss
+++ b/web/sass-files/sass/partials/_content.scss
@@ -1,9 +1,7 @@
@charset "UTF-8";
.inner__wrap {
- @include single-transition(all, 0.5s, ease);
&.move--left {
- margin-right: 400px;
.search-bar__container {
display: none;
}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 2c341f61e..62e067437 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -257,3 +257,8 @@
@include opacity(0);
}
}
+
+.view-image__loading {
+ background: black;
+ min-height: 100px;
+}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 6f70e502f..7627f6a4c 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -229,6 +229,12 @@
height: 80%;
}
+ audio, canvas, progress, video {
+ max-width: 100%;
+ height: auto;
+ display: block;
+ }
+
.modal-close {
background: url("../images/close.png") no-repeat;
@include background-size(100% 100%);
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
index 1ae07fe5b..8a61758f1 100644
--- a/web/sass-files/sass/partials/_popover.scss
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -85,6 +85,11 @@
background: rgba(black, 0.2);
}
+ .fa {
+ margin-right: 5px;
+ @include opacity(0.5);
+ }
+
.profile-img {
margin-top: -1px;
height: 16px;
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index ff430ba8a..937b08084 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -211,6 +211,10 @@ body.ios {
overflow-y: hidden;
height: 100%;
+ .inactive {
+ display: none;
+ }
+
.post-list-holder-by-time {
background: #fff;
overflow-y: scroll;
@@ -222,21 +226,18 @@ body.ios {
&::-webkit-scrollbar {
width: 0px !important;
}
- &.inactive {
- display: none;
- }
&.active {
display: inline;
}
}
.more-messages-text {
- margin-top: 2px;
- margin-bottom: 5px;
+ margin: 5px 0 10px;
display: block;
text-align: center;
outline: none;
border: none;
+ font-size: 13px;
}
.beginning-messages-text {
margin-top: 2px;
@@ -247,6 +248,50 @@ body.ios {
}
}
+.post-list__timestamp {
+ position: absolute;
+ top: 8px;
+ left: 50%;
+ z-index: 50;
+ width: 120px;
+ text-align: center;
+ background: $primary-color;
+ color: #fff;
+ @include border-radius(3px);
+ font-size: 12px;
+ line-height: 25px;
+ margin-left: -60px;
+ -webkit-font-smoothing: initial;
+ @include single-transition(all, 0.3s, ease);
+ @include translateY(-45px);
+ @include opacity(0);
+ display: none;
+
+ &.scrolling {
+ @include single-transition(all, 0.3s, ease);
+ @include translateY(0);
+ @include opacity(0.8);
+ }
+}
+
+.post-list__arrows {
+ background: url('../images/postArrows.png') center;
+ @include background-size(28px 28px);
+ background-repeat: no-repeat;
+ width: 40px;
+ height: 40px;
+ position: absolute;
+ bottom: 50px;
+ right: 5px;
+ z-index: 50;
+ @include opacity(0);
+ @include single-transition(all, 0.3s);
+
+ &.scrolling {
+ @include opacity(1);
+ }
+}
+
.post-create__container {
form {
width: 100%;
@@ -379,7 +424,7 @@ body.ios {
p {
- margin: 0 0 1em;
+ margin: 0;
line-height: 1.6em;
font-size: 0.97em;
white-space: pre-wrap;
@@ -631,9 +676,14 @@ body.ios {
}
ul {
+ margin-bottom: 0.6em;
padding: 5px 0 0 20px;
}
+ ul + p {
+ margin-top: 1em;
+ }
+
ul, ol {
p {
margin-bottom: 0;
@@ -683,7 +733,7 @@ body.ios {
}
.post__time {
- font-size: 13px;
+ font-size: 0.9em;
}
.post__time, &.post--system .post__body {
diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss
index fa52e2972..d820447f5 100644
--- a/web/sass-files/sass/partials/_post_right.scss
+++ b/web/sass-files/sass/partials/_post_right.scss
@@ -17,7 +17,8 @@
.post {
&.post--root {
- padding-bottom: 0;
+ padding-bottom: 1.2em;
+ border-bottom: 1px solid #ddd;
}
.post__header {
@@ -36,6 +37,7 @@
hr {
margin-bottom: 0;
+ border: none;
}
.post-create__container {
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 2aa130fa9..8491869a6 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -56,6 +56,7 @@
.sidebar--right {
z-index: 5;
@include translateX(100%);
+ @include single-transition(all, 0.5s, ease);
&.move--left {
@include translateX(0);
@@ -241,6 +242,9 @@
}
}
}
+ .post-list__timestamp {
+ display: block;
+ }
.signup-team__container {
padding: 30px 0;
margin-bottom: 30px;
@@ -295,15 +299,6 @@
.settings-content {
&.minimize-settings {
display: block;
- .section-edit {
- position: absolute;
- top: 7px;
- right: 0;
- width: 50px;
- .fa {
- display: inline-block;
- }
- }
}
.section-min:hover {
background: none;
@@ -336,12 +331,24 @@
}
}
.settings-table {
- .nav, .nav.absolute {
+ .nav {
+ &.position--top {
+ top: auto;
+ }
position: relative;
top: auto;
width: 100%;
}
.settings-content {
+ .section-edit {
+ position: absolute;
+ top: 14px;
+ right: 0;
+ padding-right: 0;
+ .fa {
+ display: inline-block;
+ }
+ }
&.minimize-settings {
padding: 0;
display: none;
@@ -349,6 +356,9 @@
padding: 70px 20px 30px;
}
}
+ .section-min:hover {
+ background: none !important;
+ }
}
.settings-links {
background: #fff;
@@ -625,6 +635,7 @@
height: calc(100% - 44px);
}
.inner__wrap {
+ @include single-transition(all, 0.5s, ease);
&.move--right {
@include translate3d(290px, 0, 0);
&:before {
@@ -728,6 +739,9 @@
}
}
@media screen and (max-width: 480px) {
+ .tip-overlay.tip-overlay--sidebar {
+ min-height: 350px;
+ }
.modal {
.modal-body {
max-height: 70%;
@@ -795,4 +809,4 @@
font-size: 2em;
}
}
-} \ No newline at end of file
+}
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index 0d9d7b50d..cb125bff0 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -18,6 +18,12 @@
z-index: 5;
cursor: pointer;
}
+
+.search-item-snippet {
+ @include clearfix;
+ text-overflow: ellipsis;
+}
+
.sidebar__collapse {
cursor: pointer;
z-index: 5;
@@ -134,7 +140,8 @@
}
.search-item-time {
- color: #a8adb7;
+ @include opacity(0.7);
+ font-size: 0.9em;
}
.search-results-none {
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 473ffb28d..bd47ca960 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -82,9 +82,8 @@
.nav {
position: fixed;
width: 179px;
- &.absolute {
- position: absolute;
- top: 0;
+ &.position--top {
+ top: 57px;
}
}
.security-links {
@@ -141,144 +140,167 @@
width: 29px;
}
}
- .premade-themes {
- margin-bottom: 10px;
- .theme-label {
- font-weight: 400;
- margin-top: 5px;
- }
- img {
- border: 3px solid transparent;
+ .group--code {
+ select {
+ padding-right: 25px;
}
- .active {
- img {
- border-color: $primary-color;
- }
+ &:before {
+ position: absolute;
+ top: 11px;
+ right: 50px;
+ z-index: 5;
+ content: "\f0d7";
+ display: inline-block;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
+ select {
+ -moz-appearance:none;
+ -webkit-appearance:none;
+ appearance:none;
+ }
+ }
+ .premade-themes {
+ margin-bottom: 10px;
+ .theme-label {
+ font-weight: 400;
+ margin-top: 5px;
}
- .custom-label {
- font-weight: normal;
- font-size: 13px;
- width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-bottom: 0;
- }
- .input-group-addon {
- background: transparent;
+ img {
+ border: 3px solid transparent;
}
- .radio {
- label {
- font-weight: 600;
+ .active {
+ img {
+ border-color: $primary-color;
}
}
}
-
- .section-title {
- margin-bottom: 5px;
- font-weight: 600;
+ .custom-label {
+ font-weight: normal;
+ font-size: 13px;
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 0;
}
-
- .section-edit {
- text-align: right;
- margin-bottom: 5px;
- .fa {
- margin-right: 5px;
- font-size: 12px;
- @include opacity(0.5);
- display: none;
+ .input-group-addon {
+ background: transparent;
+ }
+ .radio {
+ label {
+ font-weight: 600;
}
}
+ }
- .section-describe {
- @include opacity(0.7);
- white-space:pre;
- }
+ .section-title {
+ margin-bottom: 5px;
+ font-weight: 600;
+ }
- .divider-dark {
- border-bottom:1px solid #aaaaaa;
+ .section-edit {
+ text-align: right;
+ margin-bottom: 5px;
+ .fa {
+ margin-right: 5px;
+ font-size: 12px;
+ @include opacity(0.5);
+ display: none;
}
+ }
- .divider-light {
- border-bottom:1px solid lightgrey;
- }
+ .section-describe {
+ @include opacity(0.7);
+ white-space:pre;
+ }
- .setting-list {
- padding: 0;
- list-style-type:none;
- }
+ .divider-dark {
+ border-bottom:1px solid #aaaaaa;
+ }
- .setting-list__hint {
- margin-top: 20px;
- }
+ .divider-light {
+ border-bottom:1px solid lightgrey;
+ }
- .mentions-input {
- margin-top: 10px;
- }
+ .setting-list {
+ padding: 0;
+ list-style-type:none;
+ }
- .setting-list-item {
- margin-top:7px;
- }
- .has-error {
- color: #a94442;
- }
+ .setting-list__hint {
+ margin-top: 20px;
+ }
- .file-status {
- font-size: 13px;
- margin-top: 8px;
- color: #555;
- }
+ .mentions-input {
+ margin-top: 10px;
+ }
- .confirm-import {
- padding: 4px 10px;
- margin: 10px 0;
- }
+ .setting-list-item {
+ margin-top:7px;
+ }
+ .has-error {
+ color: #a94442;
+ }
+ .file-status {
+ font-size: 13px;
+ margin-top: 8px;
+ color: #555;
}
+
+ .confirm-import {
+ padding: 4px 10px;
+ margin: 10px 0;
+ }
+
}
- .nav-pills {
- > li {
- margin: 0;
+}
+.nav-pills {
+ > li {
+ margin: 0;
+ a {
+ border-radius: 0;
+ color: #777;
+ }
+ .glyphicon {
+ width: 25px;
+ top: 2px;
+ }
+ &:hover {
a {
- border-radius: 0;
- color: #777;
+ background: #E6F2FA;
}
- .glyphicon {
- width: 25px;
- top: 2px;
- }
- &:hover {
- a {
- background: #E6F2FA;
+ }
+ &.active {
+ a {
+ color: #111;
+ background-color: #E1E1E1;
+ &:before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 5px;
+ height: 100%;
+ background: #000;
}
}
- &.active {
- a {
- color: #111;
- background-color: #E1E1E1;
- &:before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 5px;
- height: 100%;
- background: #000;
- }
- }
- a, a:hover, a:focus {
- padding-right: 10px;
- background-color: rgba(black, 0.1);
- border-radius: 0;
- font-weight: 400;
- position: relative;
- }
+ a, a:hover, a:focus {
+ padding-right: 10px;
+ background-color: rgba(black, 0.1);
+ border-radius: 0;
+ font-weight: 400;
+ position: relative;
}
}
}
- h3 {
- font-size: em(20px);
- }
+}
+h3 {
+ font-size: em(20px);
+}
}
.channel-settings {
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index e39f7730b..f40a50b03 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -7,13 +7,7 @@
right: 0px;
padding: 0;
background: #fff;
- @include single-transition(transform, 0.5s, ease);
@include translateX(400px);
-
- &.move--left {
- @include translateX(0);
- }
-
.post-body {
img {
diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss
index 9e5e1ad7a..20a15441e 100644
--- a/web/sass-files/sass/partials/_tutorial.scss
+++ b/web/sass-files/sass/partials/_tutorial.scss
@@ -32,6 +32,14 @@
&.tip-overlay--sidebar {
max-width: 75%;
margin: 50px 0 0 10px;
+ min-height: 310px;
+ .tutorial__footer {
+ position: absolute;
+ width: 100%;
+ bottom: 20px;
+ left: 0;
+ padding: 0 20px;
+ }
.arrow {
top: 80px;
left: -10px;
diff --git a/web/static/images/postArrows.png b/web/static/images/postArrows.png
new file mode 100644
index 000000000..7b5919fc3
--- /dev/null
+++ b/web/static/images/postArrows.png
Binary files differ
diff --git a/web/static/js/velocity.min.js b/web/static/js/velocity.min.js
new file mode 100644
index 000000000..100360353
--- /dev/null
+++ b/web/static/js/velocity.min.js
@@ -0,0 +1,4 @@
+/*! VelocityJS.org (1.2.3). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */
+/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */
+!function(a){function b(a){var b=a.length,d=c.type(a);return"function"===d||c.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===d||0===b||"number"==typeof b&&b>0&&b-1 in a}if(!a.jQuery){var c=function(a,b){return new c.fn.init(a,b)};c.isWindow=function(a){return null!=a&&a==a.window},c.type=function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?e[g.call(a)]||"object":typeof a},c.isArray=Array.isArray||function(a){return"array"===c.type(a)},c.isPlainObject=function(a){var b;if(!a||"object"!==c.type(a)||a.nodeType||c.isWindow(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(d){return!1}for(b in a);return void 0===b||f.call(a,b)},c.each=function(a,c,d){var e,f=0,g=a.length,h=b(a);if(d){if(h)for(;g>f&&(e=c.apply(a[f],d),e!==!1);f++);else for(f in a)if(e=c.apply(a[f],d),e===!1)break}else if(h)for(;g>f&&(e=c.call(a[f],f,a[f]),e!==!1);f++);else for(f in a)if(e=c.call(a[f],f,a[f]),e===!1)break;return a},c.data=function(a,b,e){if(void 0===e){var f=a[c.expando],g=f&&d[f];if(void 0===b)return g;if(g&&b in g)return g[b]}else if(void 0!==b){var f=a[c.expando]||(a[c.expando]=++c.uuid);return d[f]=d[f]||{},d[f][b]=e,e}},c.removeData=function(a,b){var e=a[c.expando],f=e&&d[e];f&&c.each(b,function(a,b){delete f[b]})},c.extend=function(){var a,b,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;for("boolean"==typeof h&&(k=h,h=arguments[i]||{},i++),"object"!=typeof h&&"function"!==c.type(h)&&(h={}),i===j&&(h=this,i--);j>i;i++)if(null!=(f=arguments[i]))for(e in f)a=h[e],d=f[e],h!==d&&(k&&d&&(c.isPlainObject(d)||(b=c.isArray(d)))?(b?(b=!1,g=a&&c.isArray(a)?a:[]):g=a&&c.isPlainObject(a)?a:{},h[e]=c.extend(k,g,d)):void 0!==d&&(h[e]=d));return h},c.queue=function(a,d,e){function f(a,c){var d=c||[];return null!=a&&(b(Object(a))?!function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;)a[e++]=b[d++];if(c!==c)for(;void 0!==b[d];)a[e++]=b[d++];return a.length=e,a}(d,"string"==typeof a?[a]:a):[].push.call(d,a)),d}if(a){d=(d||"fx")+"queue";var g=c.data(a,d);return e?(!g||c.isArray(e)?g=c.data(a,d,f(e)):g.push(e),g):g||[]}},c.dequeue=function(a,b){c.each(a.nodeType?[a]:a,function(a,d){b=b||"fx";var e=c.queue(d,b),f=e.shift();"inprogress"===f&&(f=e.shift()),f&&("fx"===b&&e.unshift("inprogress"),f.call(d,function(){c.dequeue(d,b)}))})},c.fn=c.prototype={init:function(a){if(a.nodeType)return this[0]=a,this;throw new Error("Not a DOM node.")},offset:function(){var b=this[0].getBoundingClientRect?this[0].getBoundingClientRect():{top:0,left:0};return{top:b.top+(a.pageYOffset||document.scrollTop||0)-(document.clientTop||0),left:b.left+(a.pageXOffset||document.scrollLeft||0)-(document.clientLeft||0)}},position:function(){function a(){for(var a=this.offsetParent||document;a&&"html"===!a.nodeType.toLowerCase&&"static"===a.style.position;)a=a.offsetParent;return a||document}var b=this[0],a=a.apply(b),d=this.offset(),e=/^(?:body|html)$/i.test(a.nodeName)?{top:0,left:0}:c(a).offset();return d.top-=parseFloat(b.style.marginTop)||0,d.left-=parseFloat(b.style.marginLeft)||0,a.style&&(e.top+=parseFloat(a.style.borderTopWidth)||0,e.left+=parseFloat(a.style.borderLeftWidth)||0),{top:d.top-e.top,left:d.left-e.left}}};var d={};c.expando="velocity"+(new Date).getTime(),c.uuid=0;for(var e={},f=e.hasOwnProperty,g=e.toString,h="Boolean Number String Function Array Date RegExp Object Error".split(" "),i=0;i<h.length;i++)e["[object "+h[i]+"]"]=h[i].toLowerCase();c.fn.init.prototype=c.fn,a.Velocity={Utilities:c}}}(window),function(a){"object"==typeof module&&"object"==typeof module.exports?module.exports=a():"function"==typeof define&&define.amd?define(a):a()}(function(){return function(a,b,c,d){function e(a){for(var b=-1,c=a?a.length:0,d=[];++b<c;){var e=a[b];e&&d.push(e)}return d}function f(a){return p.isWrapped(a)?a=[].slice.call(a):p.isNode(a)&&(a=[a]),a}function g(a){var b=m.data(a,"velocity");return null===b?d:b}function h(a){return function(b){return Math.round(b*a)*(1/a)}}function i(a,c,d,e){function f(a,b){return 1-3*b+3*a}function g(a,b){return 3*b-6*a}function h(a){return 3*a}function i(a,b,c){return((f(b,c)*a+g(b,c))*a+h(b))*a}function j(a,b,c){return 3*f(b,c)*a*a+2*g(b,c)*a+h(b)}function k(b,c){for(var e=0;p>e;++e){var f=j(c,a,d);if(0===f)return c;var g=i(c,a,d)-b;c-=g/f}return c}function l(){for(var b=0;t>b;++b)x[b]=i(b*u,a,d)}function m(b,c,e){var f,g,h=0;do g=c+(e-c)/2,f=i(g,a,d)-b,f>0?e=g:c=g;while(Math.abs(f)>r&&++h<s);return g}function n(b){for(var c=0,e=1,f=t-1;e!=f&&x[e]<=b;++e)c+=u;--e;var g=(b-x[e])/(x[e+1]-x[e]),h=c+g*u,i=j(h,a,d);return i>=q?k(b,h):0==i?h:m(b,c,c+u)}function o(){y=!0,(a!=c||d!=e)&&l()}var p=4,q=.001,r=1e-7,s=10,t=11,u=1/(t-1),v="Float32Array"in b;if(4!==arguments.length)return!1;for(var w=0;4>w;++w)if("number"!=typeof arguments[w]||isNaN(arguments[w])||!isFinite(arguments[w]))return!1;a=Math.min(a,1),d=Math.min(d,1),a=Math.max(a,0),d=Math.max(d,0);var x=v?new Float32Array(t):new Array(t),y=!1,z=function(b){return y||o(),a===c&&d===e?b:0===b?0:1===b?1:i(n(b),c,e)};z.getControlPoints=function(){return[{x:a,y:c},{x:d,y:e}]};var A="generateBezier("+[a,c,d,e]+")";return z.toString=function(){return A},z}function j(a,b){var c=a;return p.isString(a)?t.Easings[a]||(c=!1):c=p.isArray(a)&&1===a.length?h.apply(null,a):p.isArray(a)&&2===a.length?u.apply(null,a.concat([b])):p.isArray(a)&&4===a.length?i.apply(null,a):!1,c===!1&&(c=t.Easings[t.defaults.easing]?t.defaults.easing:s),c}function k(a){if(a){var b=(new Date).getTime(),c=t.State.calls.length;c>1e4&&(t.State.calls=e(t.State.calls));for(var f=0;c>f;f++)if(t.State.calls[f]){var h=t.State.calls[f],i=h[0],j=h[2],n=h[3],o=!!n,q=null;n||(n=t.State.calls[f][3]=b-16);for(var r=Math.min((b-n)/j.duration,1),s=0,u=i.length;u>s;s++){var w=i[s],y=w.element;if(g(y)){var z=!1;if(j.display!==d&&null!==j.display&&"none"!==j.display){if("flex"===j.display){var A=["-webkit-box","-moz-box","-ms-flexbox","-webkit-flex"];m.each(A,function(a,b){v.setPropertyValue(y,"display",b)})}v.setPropertyValue(y,"display",j.display)}j.visibility!==d&&"hidden"!==j.visibility&&v.setPropertyValue(y,"visibility",j.visibility);for(var B in w)if("element"!==B){var C,D=w[B],E=p.isString(D.easing)?t.Easings[D.easing]:D.easing;if(1===r)C=D.endValue;else{var F=D.endValue-D.startValue;if(C=D.startValue+F*E(r,j,F),!o&&C===D.currentValue)continue}if(D.currentValue=C,"tween"===B)q=C;else{if(v.Hooks.registered[B]){var G=v.Hooks.getRoot(B),H=g(y).rootPropertyValueCache[G];H&&(D.rootPropertyValue=H)}var I=v.setPropertyValue(y,B,D.currentValue+(0===parseFloat(C)?"":D.unitType),D.rootPropertyValue,D.scrollData);v.Hooks.registered[B]&&(g(y).rootPropertyValueCache[G]=v.Normalizations.registered[G]?v.Normalizations.registered[G]("extract",null,I[1]):I[1]),"transform"===I[0]&&(z=!0)}}j.mobileHA&&g(y).transformCache.translate3d===d&&(g(y).transformCache.translate3d="(0px, 0px, 0px)",z=!0),z&&v.flushTransformCache(y)}}j.display!==d&&"none"!==j.display&&(t.State.calls[f][2].display=!1),j.visibility!==d&&"hidden"!==j.visibility&&(t.State.calls[f][2].visibility=!1),j.progress&&j.progress.call(h[1],h[1],r,Math.max(0,n+j.duration-b),n,q),1===r&&l(f)}}t.State.isTicking&&x(k)}function l(a,b){if(!t.State.calls[a])return!1;for(var c=t.State.calls[a][0],e=t.State.calls[a][1],f=t.State.calls[a][2],h=t.State.calls[a][4],i=!1,j=0,k=c.length;k>j;j++){var l=c[j].element;if(b||f.loop||("none"===f.display&&v.setPropertyValue(l,"display",f.display),"hidden"===f.visibility&&v.setPropertyValue(l,"visibility",f.visibility)),f.loop!==!0&&(m.queue(l)[1]===d||!/\.velocityQueueEntryFlag/i.test(m.queue(l)[1]))&&g(l)){g(l).isAnimating=!1,g(l).rootPropertyValueCache={};var n=!1;m.each(v.Lists.transforms3D,function(a,b){var c=/^scale/.test(b)?1:0,e=g(l).transformCache[b];g(l).transformCache[b]!==d&&new RegExp("^\\("+c+"[^.]").test(e)&&(n=!0,delete g(l).transformCache[b])}),f.mobileHA&&(n=!0,delete g(l).transformCache.translate3d),n&&v.flushTransformCache(l),v.Values.removeClass(l,"velocity-animating")}if(!b&&f.complete&&!f.loop&&j===k-1)try{f.complete.call(e,e)}catch(o){setTimeout(function(){throw o},1)}h&&f.loop!==!0&&h(e),g(l)&&f.loop===!0&&!b&&(m.each(g(l).tweensContainer,function(a,b){/^rotate/.test(a)&&360===parseFloat(b.endValue)&&(b.endValue=0,b.startValue=360),/^backgroundPosition/.test(a)&&100===parseFloat(b.endValue)&&"%"===b.unitType&&(b.endValue=0,b.startValue=100)}),t(l,"reverse",{loop:!0,delay:f.delay})),f.queue!==!1&&m.dequeue(l,f.queue)}t.State.calls[a]=!1;for(var p=0,q=t.State.calls.length;q>p;p++)if(t.State.calls[p]!==!1){i=!0;break}i===!1&&(t.State.isTicking=!1,delete t.State.calls,t.State.calls=[])}var m,n=function(){if(c.documentMode)return c.documentMode;for(var a=7;a>4;a--){var b=c.createElement("div");if(b.innerHTML="<!--[if IE "+a+"]><span></span><![endif]-->",b.getElementsByTagName("span").length)return b=null,a}return d}(),o=function(){var a=0;return b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||function(b){var c,d=(new Date).getTime();return c=Math.max(0,16-(d-a)),a=d+c,setTimeout(function(){b(d+c)},c)}}(),p={isString:function(a){return"string"==typeof a},isArray:Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},isFunction:function(a){return"[object Function]"===Object.prototype.toString.call(a)},isNode:function(a){return a&&a.nodeType},isNodeList:function(a){return"object"==typeof a&&/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(a))&&a.length!==d&&(0===a.length||"object"==typeof a[0]&&a[0].nodeType>0)},isWrapped:function(a){return a&&(a.jquery||b.Zepto&&b.Zepto.zepto.isZ(a))},isSVG:function(a){return b.SVGElement&&a instanceof b.SVGElement},isEmptyObject:function(a){for(var b in a)return!1;return!0}},q=!1;if(a.fn&&a.fn.jquery?(m=a,q=!0):m=b.Velocity.Utilities,8>=n&&!q)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(7>=n)return void(jQuery.fn.velocity=jQuery.fn.animate);var r=400,s="swing",t={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),isAndroid:/Android/i.test(navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(navigator.userAgent),isChrome:b.chrome,isFirefox:/Firefox/i.test(navigator.userAgent),prefixElement:c.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[]},CSS:{},Utilities:m,Redirects:{},Easings:{},Promise:b.Promise,defaults:{queue:"",duration:r,easing:s,begin:d,complete:d,progress:d,display:d,visibility:d,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0},init:function(a){m.data(a,"velocity",{isSVG:p.isSVG(a),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:2,patch:2},debug:!1};b.pageYOffset!==d?(t.State.scrollAnchor=b,t.State.scrollPropertyLeft="pageXOffset",t.State.scrollPropertyTop="pageYOffset"):(t.State.scrollAnchor=c.documentElement||c.body.parentNode||c.body,t.State.scrollPropertyLeft="scrollLeft",t.State.scrollPropertyTop="scrollTop");var u=function(){function a(a){return-a.tension*a.x-a.friction*a.v}function b(b,c,d){var e={x:b.x+d.dx*c,v:b.v+d.dv*c,tension:b.tension,friction:b.friction};return{dx:e.v,dv:a(e)}}function c(c,d){var e={dx:c.v,dv:a(c)},f=b(c,.5*d,e),g=b(c,.5*d,f),h=b(c,d,g),i=1/6*(e.dx+2*(f.dx+g.dx)+h.dx),j=1/6*(e.dv+2*(f.dv+g.dv)+h.dv);return c.x=c.x+i*d,c.v=c.v+j*d,c}return function d(a,b,e){var f,g,h,i={x:-1,v:0,tension:null,friction:null},j=[0],k=0,l=1e-4,m=.016;for(a=parseFloat(a)||500,b=parseFloat(b)||20,e=e||null,i.tension=a,i.friction=b,f=null!==e,f?(k=d(a,b),g=k/e*m):g=m;;)if(h=c(h||i,g),j.push(1+h.x),k+=16,!(Math.abs(h.x)>l&&Math.abs(h.v)>l))break;return f?function(a){return j[a*(j.length-1)|0]}:k}}();t.Easings={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},spring:function(a){return 1-Math.cos(4.5*a*Math.PI)*Math.exp(6*-a)}},m.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(a,b){t.Easings[b[0]]=i.apply(null,b[1])});var v=t.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"]},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var a=0;a<v.Lists.colors.length;a++){var b="color"===v.Lists.colors[a]?"0 0 0 1":"255 255 255 1";v.Hooks.templates[v.Lists.colors[a]]=["Red Green Blue Alpha",b]}var c,d,e;if(n)for(c in v.Hooks.templates){d=v.Hooks.templates[c],e=d[0].split(" ");var f=d[1].match(v.RegEx.valueSplit);"Color"===e[0]&&(e.push(e.shift()),f.push(f.shift()),v.Hooks.templates[c]=[e.join(" "),f.join(" ")])}for(c in v.Hooks.templates){d=v.Hooks.templates[c],e=d[0].split(" ");for(var a in e){var g=c+e[a],h=a;v.Hooks.registered[g]=[c,h]}}},getRoot:function(a){var b=v.Hooks.registered[a];return b?b[0]:a},cleanRootPropertyValue:function(a,b){return v.RegEx.valueUnwrap.test(b)&&(b=b.match(v.RegEx.valueUnwrap)[1]),v.Values.isCSSNullValue(b)&&(b=v.Hooks.templates[a][1]),b},extractValue:function(a,b){var c=v.Hooks.registered[a];if(c){var d=c[0],e=c[1];return b=v.Hooks.cleanRootPropertyValue(d,b),b.toString().match(v.RegEx.valueSplit)[e]}return b},injectValue:function(a,b,c){var d=v.Hooks.registered[a];if(d){var e,f,g=d[0],h=d[1];return c=v.Hooks.cleanRootPropertyValue(g,c),e=c.toString().match(v.RegEx.valueSplit),e[h]=b,f=e.join(" ")}return c}},Normalizations:{registered:{clip:function(a,b,c){switch(a){case"name":return"clip";case"extract":var d;return v.RegEx.wrappedValueAlreadyExtracted.test(c)?d=c:(d=c.toString().match(v.RegEx.valueUnwrap),d=d?d[1].replace(/,(\s+)?/g," "):c),d;case"inject":return"rect("+c+")"}},blur:function(a,b,c){switch(a){case"name":return t.State.isFirefox?"filter":"-webkit-filter";case"extract":var d=parseFloat(c);if(!d&&0!==d){var e=c.toString().match(/blur\(([0-9]+[A-z]+)\)/i);d=e?e[1]:0}return d;case"inject":return parseFloat(c)?"blur("+c+")":"none"}},opacity:function(a,b,c){if(8>=n)switch(a){case"name":return"filter";case"extract":var d=c.toString().match(/alpha\(opacity=(.*)\)/i);return c=d?d[1]/100:1;case"inject":return b.style.zoom=1,parseFloat(c)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(c),10)+")"}else switch(a){case"name":return"opacity";case"extract":return c;case"inject":return c}}},register:function(){9>=n||t.State.isGingerbread||(v.Lists.transformsBase=v.Lists.transformsBase.concat(v.Lists.transforms3D));for(var a=0;a<v.Lists.transformsBase.length;a++)!function(){var b=v.Lists.transformsBase[a];v.Normalizations.registered[b]=function(a,c,e){switch(a){case"name":return"transform";case"extract":return g(c)===d||g(c).transformCache[b]===d?/^scale/i.test(b)?1:0:g(c).transformCache[b].replace(/[()]/g,"");case"inject":var f=!1;switch(b.substr(0,b.length-1)){case"translate":f=!/(%|px|em|rem|vw|vh|\d)$/i.test(e);break;case"scal":case"scale":t.State.isAndroid&&g(c).transformCache[b]===d&&1>e&&(e=1),f=!/(\d)$/i.test(e);break;case"skew":f=!/(deg|\d)$/i.test(e);break;case"rotate":f=!/(deg|\d)$/i.test(e)}return f||(g(c).transformCache[b]="("+e+")"),g(c).transformCache[b]}}}();for(var a=0;a<v.Lists.colors.length;a++)!function(){var b=v.Lists.colors[a];v.Normalizations.registered[b]=function(a,c,e){switch(a){case"name":return b;case"extract":var f;if(v.RegEx.wrappedValueAlreadyExtracted.test(e))f=e;else{var g,h={black:"rgb(0, 0, 0)",blue:"rgb(0, 0, 255)",gray:"rgb(128, 128, 128)",green:"rgb(0, 128, 0)",red:"rgb(255, 0, 0)",white:"rgb(255, 255, 255)"};/^[A-z]+$/i.test(e)?g=h[e]!==d?h[e]:h.black:v.RegEx.isHex.test(e)?g="rgb("+v.Values.hexToRgb(e).join(" ")+")":/^rgba?\(/i.test(e)||(g=h.black),f=(g||e).toString().match(v.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g," ")}return 8>=n||3!==f.split(" ").length||(f+=" 1"),f;case"inject":return 8>=n?4===e.split(" ").length&&(e=e.split(/\s+/).slice(0,3).join(" ")):3===e.split(" ").length&&(e+=" 1"),(8>=n?"rgb":"rgba")+"("+e.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")"}}}()}},Names:{camelCase:function(a){return a.replace(/-(\w)/g,function(a,b){return b.toUpperCase()})},SVGAttribute:function(a){var b="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(n||t.State.isAndroid&&!t.State.isChrome)&&(b+="|transform"),new RegExp("^("+b+")$","i").test(a)},prefixCheck:function(a){if(t.State.prefixMatches[a])return[t.State.prefixMatches[a],!0];for(var b=["","Webkit","Moz","ms","O"],c=0,d=b.length;d>c;c++){var e;if(e=0===c?a:b[c]+a.replace(/^\w/,function(a){return a.toUpperCase()}),p.isString(t.State.prefixElement.style[e]))return t.State.prefixMatches[a]=e,[e,!0]}return[a,!1]}},Values:{hexToRgb:function(a){var b,c=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,d=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;return a=a.replace(c,function(a,b,c,d){return b+b+c+c+d+d}),b=d.exec(a),b?[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]:[0,0,0]},isCSSNullValue:function(a){return 0==a||/^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(a)},getUnitType:function(a){return/^(rotate|skew)/i.test(a)?"deg":/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(a)?"":"px"},getDisplayType:function(a){var b=a&&a.tagName.toString().toLowerCase();return/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(b)?"inline":/^(li)$/i.test(b)?"list-item":/^(tr)$/i.test(b)?"table-row":/^(table)$/i.test(b)?"table":/^(tbody)$/i.test(b)?"table-row-group":"block"},addClass:function(a,b){a.classList?a.classList.add(b):a.className+=(a.className.length?" ":"")+b},removeClass:function(a,b){a.classList?a.classList.remove(b):a.className=a.className.toString().replace(new RegExp("(^|\\s)"+b.split(" ").join("|")+"(\\s|$)","gi")," ")}},getPropertyValue:function(a,c,e,f){function h(a,c){function e(){j&&v.setPropertyValue(a,"display","none")}var i=0;if(8>=n)i=m.css(a,c);else{var j=!1;if(/^(width|height)$/.test(c)&&0===v.getPropertyValue(a,"display")&&(j=!0,v.setPropertyValue(a,"display",v.Values.getDisplayType(a))),!f){if("height"===c&&"border-box"!==v.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var k=a.offsetHeight-(parseFloat(v.getPropertyValue(a,"borderTopWidth"))||0)-(parseFloat(v.getPropertyValue(a,"borderBottomWidth"))||0)-(parseFloat(v.getPropertyValue(a,"paddingTop"))||0)-(parseFloat(v.getPropertyValue(a,"paddingBottom"))||0);return e(),k}if("width"===c&&"border-box"!==v.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var l=a.offsetWidth-(parseFloat(v.getPropertyValue(a,"borderLeftWidth"))||0)-(parseFloat(v.getPropertyValue(a,"borderRightWidth"))||0)-(parseFloat(v.getPropertyValue(a,"paddingLeft"))||0)-(parseFloat(v.getPropertyValue(a,"paddingRight"))||0);return e(),l}}var o;o=g(a)===d?b.getComputedStyle(a,null):g(a).computedStyle?g(a).computedStyle:g(a).computedStyle=b.getComputedStyle(a,null),"borderColor"===c&&(c="borderTopColor"),i=9===n&&"filter"===c?o.getPropertyValue(c):o[c],(""===i||null===i)&&(i=a.style[c]),e()}if("auto"===i&&/^(top|right|bottom|left)$/i.test(c)){var p=h(a,"position");("fixed"===p||"absolute"===p&&/top|left/i.test(c))&&(i=m(a).position()[c]+"px")}return i}var i;if(v.Hooks.registered[c]){var j=c,k=v.Hooks.getRoot(j);e===d&&(e=v.getPropertyValue(a,v.Names.prefixCheck(k)[0])),v.Normalizations.registered[k]&&(e=v.Normalizations.registered[k]("extract",a,e)),i=v.Hooks.extractValue(j,e)}else if(v.Normalizations.registered[c]){var l,o;l=v.Normalizations.registered[c]("name",a),"transform"!==l&&(o=h(a,v.Names.prefixCheck(l)[0]),v.Values.isCSSNullValue(o)&&v.Hooks.templates[c]&&(o=v.Hooks.templates[c][1])),i=v.Normalizations.registered[c]("extract",a,o)}if(!/^[\d-]/.test(i))if(g(a)&&g(a).isSVG&&v.Names.SVGAttribute(c))if(/^(height|width)$/i.test(c))try{i=a.getBBox()[c]}catch(p){i=0}else i=a.getAttribute(c);else i=h(a,v.Names.prefixCheck(c)[0]);return v.Values.isCSSNullValue(i)&&(i=0),t.debug>=2&&console.log("Get "+c+": "+i),i},setPropertyValue:function(a,c,d,e,f){var h=c;if("scroll"===c)f.container?f.container["scroll"+f.direction]=d:"Left"===f.direction?b.scrollTo(d,f.alternateValue):b.scrollTo(f.alternateValue,d);else if(v.Normalizations.registered[c]&&"transform"===v.Normalizations.registered[c]("name",a))v.Normalizations.registered[c]("inject",a,d),h="transform",d=g(a).transformCache[c];else{if(v.Hooks.registered[c]){var i=c,j=v.Hooks.getRoot(c);e=e||v.getPropertyValue(a,j),d=v.Hooks.injectValue(i,d,e),c=j}if(v.Normalizations.registered[c]&&(d=v.Normalizations.registered[c]("inject",a,d),c=v.Normalizations.registered[c]("name",a)),h=v.Names.prefixCheck(c)[0],8>=n)try{a.style[h]=d}catch(k){t.debug&&console.log("Browser does not support ["+d+"] for ["+h+"]")}else g(a)&&g(a).isSVG&&v.Names.SVGAttribute(c)?a.setAttribute(c,d):a.style[h]=d;t.debug>=2&&console.log("Set "+c+" ("+h+"): "+d)}return[h,d]},flushTransformCache:function(a){function b(b){return parseFloat(v.getPropertyValue(a,b))}var c="";if((n||t.State.isAndroid&&!t.State.isChrome)&&g(a).isSVG){var d={translate:[b("translateX"),b("translateY")],skewX:[b("skewX")],skewY:[b("skewY")],scale:1!==b("scale")?[b("scale"),b("scale")]:[b("scaleX"),b("scaleY")],rotate:[b("rotateZ"),0,0]};m.each(g(a).transformCache,function(a){/^translate/i.test(a)?a="translate":/^scale/i.test(a)?a="scale":/^rotate/i.test(a)&&(a="rotate"),d[a]&&(c+=a+"("+d[a].join(" ")+") ",delete d[a])})}else{var e,f;m.each(g(a).transformCache,function(b){return e=g(a).transformCache[b],"transformPerspective"===b?(f=e,!0):(9===n&&"rotateZ"===b&&(b="rotate"),void(c+=b+e+" "))}),f&&(c="perspective"+f+" "+c)}v.setPropertyValue(a,"transform",c)}};v.Hooks.register(),v.Normalizations.register(),t.hook=function(a,b,c){var e=d;return a=f(a),m.each(a,function(a,f){if(g(f)===d&&t.init(f),c===d)e===d&&(e=t.CSS.getPropertyValue(f,b));else{var h=t.CSS.setPropertyValue(f,b,c);"transform"===h[0]&&t.CSS.flushTransformCache(f),e=h}}),e};var w=function(){function a(){return h?B.promise||null:i}function e(){function a(){function a(a,b){var c=d,e=d,g=d;return p.isArray(a)?(c=a[0],!p.isArray(a[1])&&/^[\d-]/.test(a[1])||p.isFunction(a[1])||v.RegEx.isHex.test(a[1])?g=a[1]:(p.isString(a[1])&&!v.RegEx.isHex.test(a[1])||p.isArray(a[1]))&&(e=b?a[1]:j(a[1],h.duration),a[2]!==d&&(g=a[2]))):c=a,b||(e=e||h.easing),p.isFunction(c)&&(c=c.call(f,y,x)),p.isFunction(g)&&(g=g.call(f,y,x)),[c||0,e,g]}function l(a,b){var c,d;return d=(b||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(a){return c=a,""}),c||(c=v.Values.getUnitType(a)),[d,c]}function n(){var a={myParent:f.parentNode||c.body,position:v.getPropertyValue(f,"position"),fontSize:v.getPropertyValue(f,"fontSize")},d=a.position===I.lastPosition&&a.myParent===I.lastParent,e=a.fontSize===I.lastFontSize;I.lastParent=a.myParent,I.lastPosition=a.position,I.lastFontSize=a.fontSize;var h=100,i={};if(e&&d)i.emToPx=I.lastEmToPx,i.percentToPxWidth=I.lastPercentToPxWidth,i.percentToPxHeight=I.lastPercentToPxHeight;else{var j=g(f).isSVG?c.createElementNS("http://www.w3.org/2000/svg","rect"):c.createElement("div");t.init(j),a.myParent.appendChild(j),m.each(["overflow","overflowX","overflowY"],function(a,b){t.CSS.setPropertyValue(j,b,"hidden")}),t.CSS.setPropertyValue(j,"position",a.position),t.CSS.setPropertyValue(j,"fontSize",a.fontSize),t.CSS.setPropertyValue(j,"boxSizing","content-box"),m.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(a,b){t.CSS.setPropertyValue(j,b,h+"%")}),t.CSS.setPropertyValue(j,"paddingLeft",h+"em"),i.percentToPxWidth=I.lastPercentToPxWidth=(parseFloat(v.getPropertyValue(j,"width",null,!0))||1)/h,i.percentToPxHeight=I.lastPercentToPxHeight=(parseFloat(v.getPropertyValue(j,"height",null,!0))||1)/h,i.emToPx=I.lastEmToPx=(parseFloat(v.getPropertyValue(j,"paddingLeft"))||1)/h,a.myParent.removeChild(j)}return null===I.remToPx&&(I.remToPx=parseFloat(v.getPropertyValue(c.body,"fontSize"))||16),null===I.vwToPx&&(I.vwToPx=parseFloat(b.innerWidth)/100,I.vhToPx=parseFloat(b.innerHeight)/100),i.remToPx=I.remToPx,i.vwToPx=I.vwToPx,i.vhToPx=I.vhToPx,t.debug>=1&&console.log("Unit ratios: "+JSON.stringify(i),f),i}if(h.begin&&0===y)try{h.begin.call(o,o)}catch(r){setTimeout(function(){throw r},1)}if("scroll"===C){var u,w,z,A=/^x$/i.test(h.axis)?"Left":"Top",D=parseFloat(h.offset)||0;h.container?p.isWrapped(h.container)||p.isNode(h.container)?(h.container=h.container[0]||h.container,u=h.container["scroll"+A],z=u+m(f).position()[A.toLowerCase()]+D):h.container=null:(u=t.State.scrollAnchor[t.State["scrollProperty"+A]],w=t.State.scrollAnchor[t.State["scrollProperty"+("Left"===A?"Top":"Left")]],z=m(f).offset()[A.toLowerCase()]+D),i={scroll:{rootPropertyValue:!1,startValue:u,currentValue:u,endValue:z,unitType:"",easing:h.easing,scrollData:{container:h.container,direction:A,alternateValue:w}},element:f},t.debug&&console.log("tweensContainer (scroll): ",i.scroll,f)}else if("reverse"===C){if(!g(f).tweensContainer)return void m.dequeue(f,h.queue);"none"===g(f).opts.display&&(g(f).opts.display="auto"),"hidden"===g(f).opts.visibility&&(g(f).opts.visibility="visible"),g(f).opts.loop=!1,g(f).opts.begin=null,g(f).opts.complete=null,s.easing||delete h.easing,s.duration||delete h.duration,h=m.extend({},g(f).opts,h);var E=m.extend(!0,{},g(f).tweensContainer);for(var F in E)if("element"!==F){var G=E[F].startValue;E[F].startValue=E[F].currentValue=E[F].endValue,E[F].endValue=G,p.isEmptyObject(s)||(E[F].easing=h.easing),t.debug&&console.log("reverse tweensContainer ("+F+"): "+JSON.stringify(E[F]),f)}i=E}else if("start"===C){var E;g(f).tweensContainer&&g(f).isAnimating===!0&&(E=g(f).tweensContainer),m.each(q,function(b,c){if(RegExp("^"+v.Lists.colors.join("$|^")+"$").test(b)){var e=a(c,!0),f=e[0],g=e[1],h=e[2];if(v.RegEx.isHex.test(f)){for(var i=["Red","Green","Blue"],j=v.Values.hexToRgb(f),k=h?v.Values.hexToRgb(h):d,l=0;l<i.length;l++){var m=[j[l]];g&&m.push(g),k!==d&&m.push(k[l]),q[b+i[l]]=m}delete q[b]}}});for(var H in q){var K=a(q[H]),L=K[0],M=K[1],N=K[2];H=v.Names.camelCase(H);var O=v.Hooks.getRoot(H),P=!1;if(g(f).isSVG||"tween"===O||v.Names.prefixCheck(O)[1]!==!1||v.Normalizations.registered[O]!==d){(h.display!==d&&null!==h.display&&"none"!==h.display||h.visibility!==d&&"hidden"!==h.visibility)&&/opacity|filter/.test(H)&&!N&&0!==L&&(N=0),h._cacheValues&&E&&E[H]?(N===d&&(N=E[H].endValue+E[H].unitType),P=g(f).rootPropertyValueCache[O]):v.Hooks.registered[H]?N===d?(P=v.getPropertyValue(f,O),N=v.getPropertyValue(f,H,P)):P=v.Hooks.templates[O][1]:N===d&&(N=v.getPropertyValue(f,H));var Q,R,S,T=!1;if(Q=l(H,N),N=Q[0],S=Q[1],Q=l(H,L),L=Q[0].replace(/^([+-\/*])=/,function(a,b){return T=b,""}),R=Q[1],N=parseFloat(N)||0,L=parseFloat(L)||0,"%"===R&&(/^(fontSize|lineHeight)$/.test(H)?(L/=100,R="em"):/^scale/.test(H)?(L/=100,R=""):/(Red|Green|Blue)$/i.test(H)&&(L=L/100*255,R="")),/[\/*]/.test(T))R=S;else if(S!==R&&0!==N)if(0===L)R=S;else{e=e||n();var U=/margin|padding|left|right|width|text|word|letter/i.test(H)||/X$/.test(H)||"x"===H?"x":"y";switch(S){case"%":N*="x"===U?e.percentToPxWidth:e.percentToPxHeight;break;case"px":break;default:N*=e[S+"ToPx"]}switch(R){case"%":N*=1/("x"===U?e.percentToPxWidth:e.percentToPxHeight);break;case"px":break;default:N*=1/e[R+"ToPx"]}}switch(T){case"+":L=N+L;break;case"-":L=N-L;break;case"*":L=N*L;break;case"/":L=N/L}i[H]={rootPropertyValue:P,startValue:N,currentValue:N,endValue:L,unitType:R,easing:M},t.debug&&console.log("tweensContainer ("+H+"): "+JSON.stringify(i[H]),f)}else t.debug&&console.log("Skipping ["+O+"] due to a lack of browser support.")}i.element=f}i.element&&(v.Values.addClass(f,"velocity-animating"),J.push(i),""===h.queue&&(g(f).tweensContainer=i,g(f).opts=h),g(f).isAnimating=!0,y===x-1?(t.State.calls.push([J,o,h,null,B.resolver]),t.State.isTicking===!1&&(t.State.isTicking=!0,k())):y++)}var e,f=this,h=m.extend({},t.defaults,s),i={};switch(g(f)===d&&t.init(f),parseFloat(h.delay)&&h.queue!==!1&&m.queue(f,h.queue,function(a){t.velocityQueueEntryFlag=!0,g(f).delayTimer={setTimeout:setTimeout(a,parseFloat(h.delay)),next:a}}),h.duration.toString().toLowerCase()){case"fast":h.duration=200;break;case"normal":h.duration=r;break;case"slow":h.duration=600;break;default:h.duration=parseFloat(h.duration)||1}t.mock!==!1&&(t.mock===!0?h.duration=h.delay=1:(h.duration*=parseFloat(t.mock)||1,h.delay*=parseFloat(t.mock)||1)),h.easing=j(h.easing,h.duration),h.begin&&!p.isFunction(h.begin)&&(h.begin=null),h.progress&&!p.isFunction(h.progress)&&(h.progress=null),h.complete&&!p.isFunction(h.complete)&&(h.complete=null),h.display!==d&&null!==h.display&&(h.display=h.display.toString().toLowerCase(),"auto"===h.display&&(h.display=t.CSS.Values.getDisplayType(f))),h.visibility!==d&&null!==h.visibility&&(h.visibility=h.visibility.toString().toLowerCase()),h.mobileHA=h.mobileHA&&t.State.isMobile&&!t.State.isGingerbread,h.queue===!1?h.delay?setTimeout(a,h.delay):a():m.queue(f,h.queue,function(b,c){return c===!0?(B.promise&&B.resolver(o),!0):(t.velocityQueueEntryFlag=!0,void a(b))}),""!==h.queue&&"fx"!==h.queue||"inprogress"===m.queue(f)[0]||m.dequeue(f)}var h,i,n,o,q,s,u=arguments[0]&&(arguments[0].p||m.isPlainObject(arguments[0].properties)&&!arguments[0].properties.names||p.isString(arguments[0].properties));if(p.isWrapped(this)?(h=!1,n=0,o=this,i=this):(h=!0,n=1,o=u?arguments[0].elements||arguments[0].e:arguments[0]),o=f(o)){u?(q=arguments[0].properties||arguments[0].p,s=arguments[0].options||arguments[0].o):(q=arguments[n],s=arguments[n+1]);var x=o.length,y=0;if(!/^(stop|finish|finishAll)$/i.test(q)&&!m.isPlainObject(s)){var z=n+1;s={};for(var A=z;A<arguments.length;A++)p.isArray(arguments[A])||!/^(fast|normal|slow)$/i.test(arguments[A])&&!/^\d/.test(arguments[A])?p.isString(arguments[A])||p.isArray(arguments[A])?s.easing=arguments[A]:p.isFunction(arguments[A])&&(s.complete=arguments[A]):s.duration=arguments[A]}var B={promise:null,resolver:null,rejecter:null};h&&t.Promise&&(B.promise=new t.Promise(function(a,b){B.resolver=a,B.rejecter=b}));var C;switch(q){case"scroll":C="scroll";break;case"reverse":C="reverse";break;case"finish":case"finishAll":case"stop":m.each(o,function(a,b){g(b)&&g(b).delayTimer&&(clearTimeout(g(b).delayTimer.setTimeout),g(b).delayTimer.next&&g(b).delayTimer.next(),delete g(b).delayTimer),"finishAll"!==q||s!==!0&&!p.isString(s)||(m.each(m.queue(b,p.isString(s)?s:""),function(a,b){p.isFunction(b)&&b()}),m.queue(b,p.isString(s)?s:"",[]))});var D=[];return m.each(t.State.calls,function(a,b){b&&m.each(b[1],function(c,e){var f=s===d?"":s;return f===!0||b[2].queue===f||s===d&&b[2].queue===!1?void m.each(o,function(c,d){d===e&&((s===!0||p.isString(s))&&(m.each(m.queue(d,p.isString(s)?s:""),function(a,b){p.isFunction(b)&&b(null,!0)
+}),m.queue(d,p.isString(s)?s:"",[])),"stop"===q?(g(d)&&g(d).tweensContainer&&f!==!1&&m.each(g(d).tweensContainer,function(a,b){b.endValue=b.currentValue}),D.push(a)):("finish"===q||"finishAll"===q)&&(b[2].duration=1))}):!0})}),"stop"===q&&(m.each(D,function(a,b){l(b,!0)}),B.promise&&B.resolver(o)),a();default:if(!m.isPlainObject(q)||p.isEmptyObject(q)){if(p.isString(q)&&t.Redirects[q]){var E=m.extend({},s),F=E.duration,G=E.delay||0;return E.backwards===!0&&(o=m.extend(!0,[],o).reverse()),m.each(o,function(a,b){parseFloat(E.stagger)?E.delay=G+parseFloat(E.stagger)*a:p.isFunction(E.stagger)&&(E.delay=G+E.stagger.call(b,a,x)),E.drag&&(E.duration=parseFloat(F)||(/^(callout|transition)/.test(q)?1e3:r),E.duration=Math.max(E.duration*(E.backwards?1-a/x:(a+1)/x),.75*E.duration,200)),t.Redirects[q].call(b,b,E||{},a,x,o,B.promise?B:d)}),a()}var H="Velocity: First argument ("+q+") was not a property map, a known action, or a registered redirect. Aborting.";return B.promise?B.rejecter(new Error(H)):console.log(H),a()}C="start"}var I={lastParent:null,lastPosition:null,lastFontSize:null,lastPercentToPxWidth:null,lastPercentToPxHeight:null,lastEmToPx:null,remToPx:null,vwToPx:null,vhToPx:null},J=[];m.each(o,function(a,b){p.isNode(b)&&e.call(b)});var K,E=m.extend({},t.defaults,s);if(E.loop=parseInt(E.loop),K=2*E.loop-1,E.loop)for(var L=0;K>L;L++){var M={delay:E.delay,progress:E.progress};L===K-1&&(M.display=E.display,M.visibility=E.visibility,M.complete=E.complete),w(o,"reverse",M)}return a()}};t=m.extend(w,t),t.animate=w;var x=b.requestAnimationFrame||o;return t.State.isMobile||c.hidden===d||c.addEventListener("visibilitychange",function(){c.hidden?(x=function(a){return setTimeout(function(){a(!0)},16)},k()):x=b.requestAnimationFrame||o}),a.Velocity=t,a!==b&&(a.fn.velocity=w,a.fn.velocity.defaults=t.defaults),m.each(["Down","Up"],function(a,b){t.Redirects["slide"+b]=function(a,c,e,f,g,h){var i=m.extend({},c),j=i.begin,k=i.complete,l={height:"",marginTop:"",marginBottom:"",paddingTop:"",paddingBottom:""},n={};i.display===d&&(i.display="Down"===b?"inline"===t.CSS.Values.getDisplayType(a)?"inline-block":"block":"none"),i.begin=function(){j&&j.call(g,g);for(var c in l){n[c]=a.style[c];var d=t.CSS.getPropertyValue(a,c);l[c]="Down"===b?[d,0]:[0,d]}n.overflow=a.style.overflow,a.style.overflow="hidden"},i.complete=function(){for(var b in n)a.style[b]=n[b];k&&k.call(g,g),h&&h.resolver(g)},t(a,l,i)}}),m.each(["In","Out"],function(a,b){t.Redirects["fade"+b]=function(a,c,e,f,g,h){var i=m.extend({},c),j={opacity:"In"===b?1:0},k=i.complete;i.complete=e!==f-1?i.begin=null:function(){k&&k.call(g,g),h&&h.resolver(g)},i.display===d&&(i.display="In"===b?"auto":"none"),t(this,j,i)}}),t}(window.jQuery||window.Zepto||window,window,document)});
diff --git a/web/templates/head.html b/web/templates/head.html
index e80f0a24c..70c94e8ff 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -36,6 +36,7 @@
<script src="/static/js/bootstrap-3.3.5.js"></script>
<script src="/static/js/bootstrap-colorpicker.min.js"></script>
<script src="/static/js/react-bootstrap-0.28.1.js"></script>
+ <script src="/static/js/velocity.min.js"></script>
<script src="/static/js/perfect-scrollbar-0.6.7.jquery.min.js"></script>
<script src="/static/js/jquery-dragster/jquery.dragster.js"></script>
<script src="/static/js/babel-polyfill-6.1.18.min.js"></script>
@@ -76,7 +77,7 @@
}
console.log('detected login from a different tab');
- window.location.href = '/';
+ window.location.href = '/' + window.mm_team.name;
}
});
});