From 83931da9f2b3eb5e8dc835313992c7ba2ea65f4a Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 11 Sep 2015 08:38:18 -0400 Subject: Fix load more posts bug. --- web/react/components/post.jsx | 2 +- web/react/components/post_list.jsx | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'web/react') diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 37de4ecc0..d3c6befd0 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -152,7 +152,7 @@ export default class Post extends React.Component { return (
{profilePic} diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 9d95887d9..e6aa3f8df 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -189,9 +189,15 @@ export default class PostList extends React.Component { this.scrollToBottom(true); // the user clicked 'load more messages' - } else if (this.gotMorePosts) { - var lastPost = oldPosts[oldOrder[prevState.numToDisplay]]; - $('#' + lastPost.id)[0].scrollIntoView(); + } else if (this.gotMorePosts && oldOrder.length > 0) { + let index; + if (prevState.numToDisplay >= oldOrder.length) { + index = oldOrder.length - 1; + } else { + index = prevState.numToDisplay; + } + const lastPost = oldPosts[oldOrder[index]]; + $('#post_' + lastPost.id)[0].scrollIntoView(); this.gotMorePosts = false; } else { this.scrollTo(this.prevScrollTop); -- cgit v1.2.3-1-g7c22 From 560805321b85b61b98f064ff6d4fa34195686f18 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Fri, 11 Sep 2015 12:46:18 -0400 Subject: Change versioning to exact versions in package.json --- web/react/package.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'web/react') diff --git a/web/react/package.json b/web/react/package.json index da55dc2b8..e55722152 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -3,22 +3,22 @@ "version": "0.0.1", "private": true, "dependencies": { - "autolinker": "^0.18.1", - "flux": "^2.1.1", - "keymirror": "^0.1.1", - "object-assign": "^3.0.0", - "react": "^0.13.3", - "react-zeroclipboard-mixin": "^0.1.0", - "twemoji": "^1.4.1" + "autolinker": "0.18.1", + "flux": "2.1.1", + "keymirror": "0.1.1", + "object-assign": "3.0.0", + "react": "0.13.3", + "react-zeroclipboard-mixin": "0.1.0", + "twemoji": "1.4.1" }, "devDependencies": { - "browserify": "^11.0.1", - "envify": "^3.4.0", - "babelify": "^6.1.3", - "uglify-js": "^2.4.24", - "watchify": "^3.3.1", - "eslint": "^1.3.1", - "eslint-plugin-react": "^3.3.1" + "browserify": "11.0.1", + "envify": "3.4.0", + "babelify": "6.1.3", + "uglify-js": "2.4.24", + "watchify": "3.3.1", + "eslint": "1.3.1", + "eslint-plugin-react": "3.3.1" }, "scripts": { "start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx", -- cgit v1.2.3-1-g7c22 From 543ce8b4bea6f432ae26846d2f1de8b5f330df83 Mon Sep 17 00:00:00 2001 From: nickago Date: Sat, 12 Sep 2015 18:07:04 -0700 Subject: WIP Added base video player --- web/react/components/view_image.jsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'web/react') diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index b0eaba5d6..8d3495e3b 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -105,6 +105,14 @@ export default class ViewImageModal extends React.Component { this.loadImage(this.state.imgId); }.bind(this)); + $('#' + this.props.modalId).on('hidden.bs.modal', function onModalHide() { + if (this.refs.video) { + var video = React.findDOMNode(this.refs.video); + video.pause(); + video.currentTime = 0; + } + }.bind(this)); + $(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) { if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) { $('.image_modal').modal('hide'); @@ -211,6 +219,16 @@ export default class ViewImageModal extends React.Component { /> ); + } else if (fileType === 'video' || fileType === 'audio') { + content = ( + + ); } else { // non-image files include a section providing details about the file var infoString = 'File type ' + fileInfo.ext.toUpperCase(); -- cgit v1.2.3-1-g7c22 From ffbe185d827aaaefb2c5ca9067980eb99b9d53b9 Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Mon, 14 Sep 2015 14:54:36 +0100 Subject: PLT-41 Automatically convert uppercase letters to lowercase in username input box. Validator not changed. --- web/react/components/team_signup_username_page.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index b5c8b14df..984c7afab 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -22,7 +22,7 @@ export default class TeamSignupUsernamePage extends React.Component { submitNext(e) { e.preventDefault(); - var name = React.findDOMNode(this.refs.name).value.trim(); + var name = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); var usernameError = Utils.isValidUsername(name); if (usernameError === 'Cannot use a reserved word as a username.') { -- cgit v1.2.3-1-g7c22 From 5fbd1e98601f2a52c0b72d9559dec7a41db7f84a Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Mon, 14 Sep 2015 15:15:48 +0100 Subject: PLT-41 Removed lowercase from the error wording. Removed additional toLowerCase within validator. --- web/react/utils/utils.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'web/react') diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 71cd1d344..879f68689 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -828,11 +828,11 @@ export function isValidUsername(name) { } else if (name.length < 3 || name.length > 15) { error = 'Must be between 3 and 15 characters'; } else if (!(/^[a-z0-9\.\-\_]+$/).test(name)) { - error = "Must contain only lowercase letters, numbers, and the symbols '.', '-', and '_'."; + error = "Must contain only letters, numbers, and the symbols '.', '-', and '_'."; } else if (!(/[a-z]/).test(name.charAt(0))) { error = 'First character must be a letter.'; } else { - var lowerName = name.toLowerCase().trim(); + var lowerName = name; for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) { if (lowerName === Constants.RESERVED_USERNAMES[i]) { -- cgit v1.2.3-1-g7c22 From 90ef55b4284e654ecae614b8d3b0c59bfba95b3b Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Mon, 14 Sep 2015 16:10:31 +0100 Subject: replace lowerName with name --- web/react/utils/utils.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'web/react') diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 879f68689..54d05f484 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -832,10 +832,8 @@ export function isValidUsername(name) { } else if (!(/[a-z]/).test(name.charAt(0))) { error = 'First character must be a letter.'; } else { - var lowerName = name; - for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) { - if (lowerName === Constants.RESERVED_USERNAMES[i]) { + if (name === Constants.RESERVED_USERNAMES[i]) { error = 'Cannot use a reserved word as a username.'; break; } -- cgit v1.2.3-1-g7c22 From 7b3c2d6d85ecee86fbc85b440e7028018b1090b1 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 14 Sep 2015 12:04:57 -0400 Subject: Allowing underscores in channel names. Added conversion of some slack channel names into valid mattermost names. --- web/react/components/team_import_tab.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx index 031abc36a..8315430e4 100644 --- a/web/react/components/team_import_tab.jsx +++ b/web/react/components/team_import_tab.jsx @@ -35,7 +35,7 @@ export default class TeamImportTab extends React.Component { var uploadHelpText = (

{'Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}

-

{'The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.'}

+

{'The Slack import to Mattermost is in "Preview". Slack bot posts do not yet import.'}

); -- cgit v1.2.3-1-g7c22 From 0ea0233c50dbccc498cb53481b9fdf18d027e5b2 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 14 Sep 2015 13:56:58 -0400 Subject: New add channel modal using react-bootstrap. --- web/react/.eslintrc | 3 +- web/react/components/change_url_modal.jsx | 177 ++++++++++++++++++++++++ web/react/components/file_upload.jsx | 2 +- web/react/components/new_channel.jsx | 211 ----------------------------- web/react/components/new_channel_flow.jsx | 206 ++++++++++++++++++++++++++++ web/react/components/new_channel_modal.jsx | 198 +++++++++++++++++++++++++++ web/react/components/sidebar.jsx | 24 ++-- web/react/package.json | 1 - web/react/pages/channel.jsx | 6 - web/react/utils/utils.jsx | 11 ++ 10 files changed, 611 insertions(+), 228 deletions(-) create mode 100644 web/react/components/change_url_modal.jsx delete mode 100644 web/react/components/new_channel.jsx create mode 100644 web/react/components/new_channel_flow.jsx create mode 100644 web/react/components/new_channel_modal.jsx (limited to 'web/react') diff --git a/web/react/.eslintrc b/web/react/.eslintrc index 53cc75913..c0d0bb200 100644 --- a/web/react/.eslintrc +++ b/web/react/.eslintrc @@ -18,7 +18,8 @@ "es6": true }, "globals": { - "React": false + "React": false, + "ReactBootstrap": false }, "rules": { "comma-dangle": [2, "never"], diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx new file mode 100644 index 000000000..28fa70c1f --- /dev/null +++ b/web/react/components/change_url_modal.jsx @@ -0,0 +1,177 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Modal = ReactBootstrap.Modal; +var Utils = require('../utils/utils.jsx'); + +export default class ChangeUrlModal extends React.Component { + constructor(props) { + super(props); + + this.onURLChanged = this.onURLChanged.bind(this); + this.doSubmit = this.doSubmit.bind(this); + this.doCancel = this.doCancel.bind(this); + + this.state = { + currentURL: props.currentURL, + urlError: '', + userEdit: false + }; + } + componentWillReceiveProps(nextProps) { + // This check prevents the url being deleted when we re-render + // because of user status check + if (!this.state.userEdit) { + this.setState({ + currentURL: nextProps.currentURL + }); + } + } + componentDidUpdate(prevProps) { + if (this.props.show === true && prevProps.show === false) { + React.findDOMNode(this.refs.urlinput).select(); + } + } + onURLChanged(e) { + const url = e.target.value.trim(); + this.setState({currentURL: url.replace(/[^A-Za-z0-9-_]/g, '').toLowerCase(), userEdit: true}); + } + getURLError(url) { + let error = []; //eslint-disable-line prefer-const + if (url.length < 2) { + error.push({'Must be longer than two characters'}
); + } + if (url.charAt(0) === '-' || url.charAt(0) === '_') { + error.push({'Must start with a letter or number'}
); + } + if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) { + error.push({'Must end with a letter or number'}
); + } + if (url.indexOf('__') > -1) { + error.push({'Can not contain two underscores in a row.'}
); + } + + // In case of error we don't detect + if (error.length === 0) { + error.push({'Invalid URL'}
); + } + return error; + } + doSubmit(e) { + e.preventDefault(); + + const url = React.findDOMNode(this.refs.urlinput).value; + const cleanedURL = Utils.cleanUpUrlable(url); + if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) { + this.setState({urlError: this.getURLError(url)}); + return; + } + this.setState({urlError: '', userEdit: false}); + this.props.onModalSubmit(url); + } + doCancel() { + this.setState({urlError: '', userEdit: false}); + this.props.onModalDismissed(); + } + render() { + let urlClass = 'input-group input-group--limit'; + let urlError = null; + let serverError = null; + + if (this.state.urlError) { + urlClass += ' has-error'; + urlError = (

{this.state.urlError}

); + } + + if (this.props.serverError) { + serverError =

{this.props.serverError}

; + } + + const fullTeamUrl = Utils.getTeamURLFromAddressBar(); + const teamURL = Utils.getShortenedTeamURL(); + + return ( + + + {this.props.title} + +
+ +
{this.props.description}
+
+ +
+
+ + {teamURL} + + +
+ {urlError} + {serverError} +
+
+
+ + + + +
+
+ ); + } +} + +ChangeUrlModal.defaultProps = { + show: false, + title: 'Change URL', + desciption: '', + urlLabel: 'URL', + submitButtonText: 'Submit', + currentURL: '', + serverError: '' +}; + +ChangeUrlModal.propTypes = { + show: React.PropTypes.bool.isRequired, + title: React.PropTypes.string, + description: React.PropTypes.string, + urlLabel: React.PropTypes.string, + submitButtonText: React.PropTypes.string, + currentURL: React.PropTypes.string, + serverError: React.PropTypes.string, + onModalSubmit: React.PropTypes.func.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 534f0136e..d7b6f08b0 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -53,7 +53,7 @@ export default class FileUpload extends React.Component { } // generate a unique id that can be used by other components to refer back to this upload - var clientId = utils.generateId(); + let clientId = utils.generateId(); // prepare data to be uploaded var formData = new FormData(); diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx deleted file mode 100644 index 1a11fc652..000000000 --- a/web/react/components/new_channel.jsx +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var asyncClient = require('../utils/async_client.jsx'); -var UserStore = require('../stores/user_store.jsx'); - -export default class NewChannelModal extends React.Component { - constructor() { - super(); - - this.handleSubmit = this.handleSubmit.bind(this); - this.displayNameKeyUp = this.displayNameKeyUp.bind(this); - this.handleClose = this.handleClose.bind(this); - - this.state = {channelType: ''}; - } - handleSubmit(e) { - e.preventDefault(); - - var channel = {}; - var state = {serverError: ''}; - - channel.display_name = React.findDOMNode(this.refs.display_name).value.trim(); - if (!channel.display_name) { - state.displayNameError = 'This field is required'; - state.inValid = true; - } else if (channel.display_name.length > 22) { - state.displayNameError = 'This field must be less than 22 characters'; - state.inValid = true; - } else { - state.displayNameError = ''; - } - - channel.name = React.findDOMNode(this.refs.channel_name).value.trim(); - if (!channel.name) { - state.nameError = 'This field is required'; - state.inValid = true; - } else if (channel.name.length > 22) { - state.nameError = 'This field must be less than 22 characters'; - state.inValid = true; - } else { - var cleanedName = utils.cleanUpUrlable(channel.name); - if (cleanedName !== channel.name) { - state.nameError = "Must be lowercase alphanumeric characters, allowing '-' but not starting or ending with '-'"; - state.inValid = true; - } else { - state.nameError = ''; - } - } - - this.setState(state); - - if (state.inValid) { - return; - } - - var cu = UserStore.getCurrentUser(); - channel.team_id = cu.team_id; - - channel.description = React.findDOMNode(this.refs.channel_desc).value.trim(); - channel.type = this.state.channelType; - - client.createChannel(channel, - function success(data) { - $(React.findDOMNode(this.refs.modal)).modal('hide'); - - asyncClient.getChannel(data.id); - utils.switchChannel(data); - - React.findDOMNode(this.refs.display_name).value = ''; - React.findDOMNode(this.refs.channel_name).value = ''; - React.findDOMNode(this.refs.channel_desc).value = ''; - }.bind(this), - function error(err) { - state.serverError = err.message; - state.inValid = true; - this.setState(state); - }.bind(this) - ); - } - displayNameKeyUp() { - var displayName = React.findDOMNode(this.refs.display_name).value.trim(); - var channelName = utils.cleanUpUrlable(displayName); - React.findDOMNode(this.refs.channel_name).value = channelName; - } - componentDidMount() { - var self = this; - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) { - var button = e.relatedTarget; - self.setState({channelType: $(button).attr('data-channeltype')}); - }); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); - } - componentWillUnmount() { - $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); - } - handleClose() { - $(React.findDOMNode(this)).find('.form-control').each(function clearForms() { - this.value = ''; - }); - - this.setState({channelType: '', displayNameError: '', nameError: '', serverError: '', inValid: false}); - } - render() { - var displayNameError = null; - var nameError = null; - var serverError = null; - var displayNameClass = 'form-group'; - var nameClass = 'form-group'; - - if (this.state.displayNameError) { - displayNameError = ; - displayNameClass += ' has-error'; - } - if (this.state.nameError) { - nameError = ; - nameClass += ' has-error'; - } - if (this.state.serverError) { - serverError =
; - } - - var channelTerm = 'Channel'; - if (this.state.channelType === 'P') { - channelTerm = 'Group'; - } - - return ( -