diff options
-rw-r--r-- | web/react/components/create_comment.jsx | 4 | ||||
-rw-r--r-- | web/react/components/create_post.jsx | 4 | ||||
-rw-r--r-- | web/react/components/file_upload.jsx | 96 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 18 | ||||
-rw-r--r-- | web/react/components/post_right.jsx | 6 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_files.scss | 15 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_post.scss | 33 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_responsive.scss | 6 | ||||
-rw-r--r-- | web/static/js/jquery-dragster/LICENSE | 21 | ||||
-rw-r--r-- | web/static/js/jquery-dragster/README.md | 17 | ||||
-rw-r--r-- | web/static/js/jquery-dragster/jquery.dragster.js | 85 | ||||
-rw-r--r-- | web/templates/head.html | 2 |
12 files changed, 290 insertions, 17 deletions
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 78e06c532..c954229ae 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -222,7 +222,9 @@ module.exports = React.createClass({ getFileCount={this.getFileCount} onUploadStart={this.handleUploadStart} onFileUpload={this.handleFileUploadComplete} - onUploadError={this.handleUploadError} /> + onUploadError={this.handleUploadError} + postType='comment' + channelId={this.props.channelId} /> </div> <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} /> <div className={postFooterClassName}> diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 9ca1d5388..137420440 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -262,7 +262,9 @@ module.exports = React.createClass({ getFileCount={this.getFileCount} onUploadStart={this.handleUploadStart} onFileUpload={this.handleFileUploadComplete} - onUploadError={this.handleUploadError} /> + onUploadError={this.handleUploadError} + postType='post' + channelId='' /> </div> <div className={postFooterClassName}> {postError} diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index c1fab669c..f1a06c361 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -21,7 +21,7 @@ module.exports = React.createClass({ var element = $(this.refs.fileInput.getDOMNode()); var files = element.prop('files'); - var channelId = ChannelStore.getCurrentId(); + var channelId = this.props.channelId || ChannelStore.getCurrentId(); this.props.onUploadError(null); @@ -87,10 +87,101 @@ module.exports = React.createClass({ } } catch(e) {} }, + handleDrop: function(e) { + this.props.onUploadError(null); + + var files = e.originalEvent.dataTransfer.files; + if (!files.length) { + files = e.originalEvent.dataTransfer.getData('URL'); + } + var channelId = this.props.channelId || ChannelStore.getCurrentId(); + + if (typeof files !== 'string' && files.length) { + var numFiles = files.length; + + var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId), numFiles); + + if (numFiles > numToUpload) { + this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.'); + } + + for (var i = 0; i < files.length && i < numToUpload; i++) { + if (files[i].size > Constants.MAX_FILE_SIZE) { + this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB'); + continue; + } + + // generate a unique id that can be used by other components to refer back to this file upload + var clientId = utils.generateId(); + + // Prepare data to be uploaded. + var formData = new FormData(); + formData.append('channel_id', channelId); + formData.append('files', files[i], files[i].name); + formData.append('client_ids', clientId); + + var request = client.uploadFile(formData, + function(data) { + var parsedData = $.parseJSON(data); + this.props.onFileUpload(parsedData['filenames'], parsedData['client_ids'], channelId); + + var requests = this.state.requests; + for (var i = 0; i < parsedData['client_ids'].length; i++) { + delete requests[parsedData['client_ids'][i]]; + } + this.setState({requests: requests}); + }.bind(this), + function(err) { + this.props.onUploadError(err, clientId); + }.bind(this) + ); + + var requests = this.state.requests; + requests[clientId] = request; + this.setState({requests: requests}); + + this.props.onUploadStart([clientId], channelId); + } + } + }, componentDidMount: function() { var inputDiv = this.refs.input.getDOMNode(); var self = this; + if (this.props.postType === 'post') { + $('.app__content').dragster({ + enter: function(dragsterEvent, e) { + $('.center-file-overlay').removeClass('invisible'); + $('.center-file-overlay').addClass('visible'); + }, + leave: function(dragsterEvent, e) { + $('.center-file-overlay').removeClass('visible'); + $('.center-file-overlay').addClass('invisible'); + }, + drop: function(dragsterEvent, e) { + $('.center-file-overlay').removeClass('visible'); + $('.center-file-overlay').addClass('invisible'); + self.handleDrop(e); + } + }); + } else if (this.props.postType === 'comment') { + $('.post-right__container').dragster({ + enter: function(dragsterEvent, e) { + $('.right-file-overlay').removeClass('invisible'); + $('.right-file-overlay').addClass('visible'); + }, + leave: function(dragsterEvent, e) { + $('.right-file-overlay').removeClass('visible'); + $('.right-file-overlay').addClass('invisible'); + }, + drop: function(dragsterEvent, e) { + $('.right-file-overlay').removeClass('visible'); + $('.right-file-overlay').addClass('invisible'); + self.handleDrop(e); + } + }); + } + document.addEventListener('paste', function(e) { var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0]; @@ -133,14 +224,13 @@ module.exports = React.createClass({ continue; } - var channelId = ChannelStore.getCurrentId(); + var channelId = this.props.channelId || ChannelStore.getCurrentId(); // generate a unique id that can be used by other components to refer back to this file upload var clientId = utils.generateId(); var formData = new FormData(); formData.append('channel_id', channelId); - var d = new Date(); var hour; if (d.getHours() < 10) { diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 83f806b79..53e8d0761 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -463,11 +463,19 @@ module.exports = React.createClass({ } return ( - <div ref="postlist" className="post-list-holder-by-time"> - <div className="post-list__table"> - <div className="post-list__content"> - { more_messages } - { postCtls } + <div> + <div className='center-file-overlay invisible'> + <div> + <i className="fa fa-upload"></i> + <span>Drop a file to upload it.</span> + </div> + </div> + <div ref="postlist" className="post-list-holder-by-time"> + <div className="post-list__table"> + <div className="post-list__content"> + { more_messages } + { postCtls } + </div> </div> </div> </div> diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index ad8b54012..b772c7ee8 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -296,6 +296,12 @@ module.exports = React.createClass({ return ( <div className="post-right__container"> + <div className='center-file-overlay right-file-overlay invisible'> + <div> + <i className="fa fa-upload"></i> + <span>Drop a file to upload it.</span> + </div> + </div> <div className="search-bar__container sidebar--right__search-header">{searchForm}</div> <div className="sidebar-right__body"> <RhsHeaderPost fromSearch={this.props.fromSearch} isMentionSearch={this.props.isMentionSearch} /> diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index 65775f01e..9bf6fd83a 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -194,11 +194,12 @@ border-right: 1px solid #ddd; vertical-align: center; - // helper to center the image icon in the preview window - .file-details__preview-helper { - height: 100%; - display: inline-block; - vertical-align: middle; - } - } + // helper to center the image icon in the preview window + .file-details__preview-helper { + height: 100%; + display: inline-block; + vertical-align: middle; } + } +} + diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 98b17120d..614fc085b 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -106,6 +106,39 @@ body.ios { } } +.center-file-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + text-align: center; + color: #FFF; + display: table; + font-size: 1.7em; + font-weight: 600; + z-index: 6; + + > div { + display: table-cell; + vertical-align: middle; + } + + .fa { + display: block; + font-size: 2em; + margin: 0 0 0.3em; + } + + .invisible { + visibility: hidden; + } + .visible { + visibility: visible; + } +} + #post-list { .post-list-holder-by-time { background: #fff; diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 47b2b6bd7..52bb5eae6 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -189,6 +189,9 @@ } @media screen and (max-width: 960px) { + .center-file-overlay { + font-size: 1.5em; + } .post { .post-header .post-header-col.post-header__reply { .comment-icon__container__hide { @@ -240,6 +243,9 @@ } } @media screen and (max-width: 768px) { + .center-file-overlay { + font-size: 1.3em; + } .date-separator, .new-separator { &.hovered--after { &:before { diff --git a/web/static/js/jquery-dragster/LICENSE b/web/static/js/jquery-dragster/LICENSE new file mode 100644 index 000000000..b8b51dc0b --- /dev/null +++ b/web/static/js/jquery-dragster/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jan Martin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web/static/js/jquery-dragster/README.md b/web/static/js/jquery-dragster/README.md new file mode 100644 index 000000000..1c28adaf0 --- /dev/null +++ b/web/static/js/jquery-dragster/README.md @@ -0,0 +1,17 @@ +Include [jquery.dragster.js](https://rawgithub.com/catmanjan/jquery-dragster/master/jquery.dragster.js) in page. + +Works in IE. + +```javascript +$('.element').dragster({ + enter: function (dragsterEvent, event) { + $(this).addClass('hover'); + }, + leave: function (dragsterEvent, event) { + $(this).removeClass('hover'); + }, + drop: function (dragsterEvent, event) { + $(this).removeClass('hover'); + } +}); +```
\ No newline at end of file diff --git a/web/static/js/jquery-dragster/jquery.dragster.js b/web/static/js/jquery-dragster/jquery.dragster.js new file mode 100644 index 000000000..db73fe3f0 --- /dev/null +++ b/web/static/js/jquery-dragster/jquery.dragster.js @@ -0,0 +1,85 @@ +// 1.0.3 +/* +The MIT License (MIT) + +Copyright (c) 2015 Jan Martin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +(function ($) { + + $.fn.dragster = function (options) { + var settings = $.extend({ + enter: $.noop, + leave: $.noop, + over: $.noop, + drop: $.noop + }, options); + + return this.each(function () { + var first = false, + second = false, + $this = $(this); + + $this.on({ + dragenter: function (event) { + if (first) { + second = true; + return; + } else { + first = true; + $this.trigger('dragster:enter', event); + } + event.preventDefault(); + }, + dragleave: function (event) { + if (second) { + second = false; + } else if (first) { + first = false; + } + if (!first && !second) { + $this.trigger('dragster:leave', event); + } + event.preventDefault(); + }, + dragover: function (event) { + $this.trigger('dragster:over', event); + event.preventDefault(); + }, + drop: function (event) { + if (second) { + second = false; + } else if (first) { + first = false; + } + if (!first && !second) { + $this.trigger('dragster:drop', event); + } + event.preventDefault(); + }, + 'dragster:enter': settings.enter, + 'dragster:leave': settings.leave, + 'dragster:over': settings.over, + 'dragster:drop': settings.drop + }); + }); + }; + +}(jQuery)); diff --git a/web/templates/head.html b/web/templates/head.html index 7a7d4fe8e..dd5e9f46e 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -32,6 +32,8 @@ <script src="/static/js/perfect-scrollbar-0.6.3.jquery.js"></script> + <script src="/static/js/jquery-dragster/jquery.dragster.js"></script> + <script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['annotationchart']}]}"></script> <script type="text/javascript" src="https://cloudfront.loggly.com/js/loggly.tracker.js" async></script> |