[NEW] allow drop files on thread (#14214)

<!-- INSTRUCTION: Your Pull Request name should start with one of the following tags -->
<!-- [NEW] For new features -->
<!-- [FIX] For bug fixes -->
<!-- [BREAK] For pull requests including breaking changes -->

<!-- INSTRUCTION: Inform the issue number that this PR closes, or remove the line below -->
Closes #14109

![image](https://user-images.githubusercontent.com/5263975/56516496-f7a24600-6510-11e9-8a27-64982ad97cd9.png)
![image](https://user-images.githubusercontent.com/5263975/56516527-0b4dac80-6511-11e9-8d54-d6033ffef9ca.png)


<!-- INSTRUCTION: Link to a https://github.com/RocketChat/docs PR with added/updated documentation or an update to the missing/outdated documentation list, see https://rocket.chat/docs/contributing/documentation/  -->

<!-- INSTRUCTION: Tell us more about your PR with screen shots if you can -->
pull/14217/head^2
Guilherme Gazzo 7 years ago committed by GitHub
parent 509cd182d3
commit 0df35a29ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 58
      app/theme/client/imports/general/base_old.css
  2. 3
      app/threads/client/flextab/thread.html
  3. 13
      app/threads/client/flextab/thread.js
  4. 277
      app/ui/client/views/app/room.html
  5. 100
      app/ui/client/views/app/room.js

@ -4683,34 +4683,68 @@ rc-old select,
height: 100%;
& .dropzone-overlay {
display: none;
}
&.over .dropzone-overlay {
position: fixed;
position: absolute;
z-index: 1000000;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
display: none;
margin: var(--default-small-padding);
padding: var(--default-padding);
animation-name: zoomIn;
animation-duration: 0.1s;
text-align: center;
color: var(--color-blue);
border: 4px dashed var(--color-blue);
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 0 var(--default-small-padding) rgba(255, 255, 255, 0.9);
font-size: 42px;
align-items: center;
justify-content: center;
& > div {
padding: 40px;
text-align: center;
pointer-events: none;
&::before {
position: absolute;
z-index: -1;
border-radius: 10px;
top: calc(-1 * var(--default-small-padding));
right: calc(-1 * var(--default-small-padding));
bottom: calc(-1 * var(--default-small-padding));
left: calc(-1 * var(--default-small-padding));
line-height: 1.3em;
content: '';
}
}
&.over .dropzone-overlay {
display: flex;
}
}
@keyframes zoomIn {
0% {
transform: scale3d(0.9, 0.9, 0.9);
opacity: 0;
}
50% {
opacity: 1;
}
}
.zoomIn {
-webkit-animation-name: zoomIn;
animation-name: zoomIn;
}
.rc-old .is-cordova {

@ -11,7 +11,8 @@
{{> icon block="contextual-bar__header-close-icon" icon="plus"}}
</button>
</header>
<section class="contextual-bar__content flex-tab threads">
<section class="contextual-bar__content flex-tab threads dropzone">
<div class="dropzone-overlay background-transparent-darkest color-content-background-color">{{_ "Drop_to_upload_file"}}</div>
<div class="thread-list js-scroll-thread">
<ul class="thread">
{{# with messageContext}}

@ -1,19 +1,24 @@
import _ from 'underscore';
import { Mongo } from 'meteor/mongo';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
import { Tracker } from 'meteor/tracker';
import { ChatMessages } from '../../../ui';
import { normalizeThreadMessage, call } from '../../../ui-utils/client';
import { messageContext } from '../../../ui-utils/client/lib/messageContext';
import { Messages } from '../../../models';
import { lazyloadtick } from '../../../lazy-load';
import { fileUpload } from '../../../ui/client/lib/fileUpload';
import { dropzoneEvents } from '../../../ui/client/views/app/room';
import { upsert } from '../upsert';
import './thread.html';
const sort = { ts: 1 };
Template.thread.events({
...dropzoneEvents,
'click .js-close'(e) {
e.preventDefault();
e.stopPropagation();
@ -24,6 +29,10 @@ Template.thread.events({
lazyloadtick();
i.atBottom = e.scrollTop >= e.scrollHeight - e.clientHeight;
}, 500),
'load img'() {
const { atBottom } = this;
atBottom && this.sendToBottom();
},
});
Template.thread.helpers({
@ -75,6 +84,10 @@ Template.thread.onRendered(function() {
this.chatMessages.initializeWrapper(this.find('.js-scroll-thread'));
this.chatMessages.initializeInput(this.find('.js-input-message'), { rid, tmid });
this.onFile = (filesToUpload) => {
fileUpload(filesToUpload, this.chatMessages.input, { rid: this.state.get('rid'), tmid: this.state.get('tmid') });
};
this.sendToBottom = _.throttle(() => {
this.chatMessages.wrapper.scrollTop = this.chatMessages.wrapper.scrollHeight;
}, 300);

@ -1,160 +1,153 @@
<template name="room">
<div class="dropzone">
<div class="dropzone-overlay background-transparent-darkest color-content-background-color">
<div class="background-transparent-darkest">
{{_ "Drop_to_upload_file"}}
</div>
</div>
<div class="main-content-flex">
<section class="messages-container flex-tab-main-content {{adminClass}}" id="{{windowId}}" aria-label="{{_ "Channel"}}">
{{# unless embeddedVersion}}
{{# headerRoom}}
{{#with flexData}}
<div class="rc-header__block rc-header__block-action">
{{> RoomsActionTab}}
</div>
{{/with}}
{{/headerRoom}}
{{/unless}}
<div class="messages-container-wrapper">
<div class="messages-container-main">
{{#unless embeddedVersion}}
{{#if showAnnouncement}}
<div class="fixed-title announcement {{getAnnouncementStyle}}" aria-label="{{RocketChatMarkdownInline roomAnnouncement}}">
<p>{{{RocketChatMarkdownInline roomAnnouncement}}}</p>
</div>
{{/if}}
{{/unless}}
<div class="container-bars {{containerBarsShow unreadData uploading}}">
{{#with unreadData}}
{{#if since}}
{{#if count}}
<div class="unread-bar color-primary-action-color background-component-color">
<button class="jump-to">
<span class="jump-to-large">{{_ "Jump_to_first_unread"}}</span>
<span class="jump-to-small">{{_ "Jump"}}</span>
</button>
<span class="unread-count-since">
{{_ "S_new_messages_since_s" count formatUnreadSince}}
</span>
<span class="unread-count">
{{_ "N_new_messages" count}}
</span>
<button class="mark-read">
{{_ "Mark_as_read"}}
</button>
</div>
{{/if}}
{{/if}}
{{/with}}
{{#each uploading}}
<div class="upload-progress color-primary-action-color background-component-color {{#if error}}error-background error-border{{/if}}">
{{#if error}}
<div class="upload-progress-text">
{{error}}
</div>
<button class="upload-progress-close">
{{_ "close"}}
<div class="main-content-flex">
<section class="messages-container flex-tab-main-content {{adminClass}}" id="{{windowId}}" aria-label="{{_ "Channel"}}">
{{# unless embeddedVersion}}
{{# headerRoom}}
{{#with flexData}}
<div class="rc-header__block rc-header__block-action">
{{> RoomsActionTab}}
</div>
{{/with}}
{{/headerRoom}}
{{/unless}}
<div class="messages-container-wrapper">
<div class="messages-container-main dropzone">
<div class="dropzone-overlay background-transparent-darkest color-content-background-color">{{_ "Drop_to_upload_file"}}</div>
{{#unless embeddedVersion}}
{{#if showAnnouncement}}
<div class="fixed-title announcement {{getAnnouncementStyle}}" aria-label="{{RocketChatMarkdownInline roomAnnouncement}}">
<p>{{{RocketChatMarkdownInline roomAnnouncement}}}</p>
</div>
{{/if}}
{{/unless}}
<div class="container-bars {{containerBarsShow unreadData uploading}}">
{{#with unreadData}}
{{#if since}}
{{#if count}}
<div class="unread-bar color-primary-action-color background-component-color">
<button class="jump-to">
<span class="jump-to-large">{{_ "Jump_to_first_unread"}}</span>
<span class="jump-to-small">{{_ "Jump"}}</span>
</button>
{{else}}
<div class="upload-progress-progress" style="width: {{percentage}}%;"></div>
<div class="upload-progress-text">
[{{percentage}}%] {{name}}
</div>
<button class="upload-progress-close">
{{_ "Cancel"}}
<span class="unread-count-since">
{{_ "S_new_messages_since_s" count formatUnreadSince}}
</span>
<span class="unread-count">
{{_ "N_new_messages" count}}
</span>
<button class="mark-read">
{{_ "Mark_as_read"}}
</button>
{{/if}}
</div>
{{/each}}
</div>
<div class="messages-box {{#if selectable}}selectable{{/if}} {{messageViewMode}} {{hasLeader}}">
<div class="ticks-bar"></div>
<button class="new-message background-primary-action-color color-content-background-color not">
<i class="icon-down-big"></i>
{{_ "New_messages"}}
</button>
<div class="jump-recent background-component-color {{#unless hasMoreNext}}not{{/unless}}">
<button>{{_ "Jump_to_recent_messages"}} <i class="icon-level-down"></i></button>
</div>
{{#unless canPreview}}
<div class="content room-not-found error-color">
<div>
{{_ "You_must_join_to_view_messages_in_this_channel"}}
</div>
</div>
{{/unless}}
{{#with roomLeader}}
<div class="room-leader message color-primary-font-color content-background-color border-component-color {{hideLeaderHeader}}">
<button class="thumb user-card-message">
{{> avatar username=username }}
{{/if}}
{{/if}}
{{/with}}
{{#each uploading}}
<div class="upload-progress color-primary-action-color background-component-color {{#if error}}error-background error-border{{/if}}">
{{#if error}}
<div class="upload-progress-text">
{{error}}
</div>
<button class="upload-progress-close">
{{_ "close"}}
</button>
<div class="leader-name">{{name}}</div>
<div class="leader-status userStatus">
<span class="color-ball status-bg-{{status}}"></span>
<span class="status-text leader-status-text">{{_ statusDisplay}}</span>
{{else}}
<div class="upload-progress-progress" style="width: {{percentage}}%;"></div>
<div class="upload-progress-text">
[{{percentage}}%] {{name}}
</div>
<a class="chat-now" href="{{chatNowLink}}">{{_ "Chat_Now"}}</a>
<button class="upload-progress-close">
{{_ "Cancel"}}
</button>
{{/if}}
</div>
{{/each}}
</div>
<div class="messages-box {{#if selectable}}selectable{{/if}} {{messageViewMode}} {{hasLeader}}">
<div class="ticks-bar"></div>
<button class="new-message background-primary-action-color color-content-background-color not">
<i class="icon-down-big"></i>
{{_ "New_messages"}}
</button>
<div class="jump-recent background-component-color {{#unless hasMoreNext}}not{{/unless}}">
<button>{{_ "Jump_to_recent_messages"}} <i class="icon-level-down"></i></button>
</div>
{{#unless canPreview}}
<div class="content room-not-found error-color">
<div>
{{_ "You_must_join_to_view_messages_in_this_channel"}}
</div>
{{/with}}
<div class="wrapper {{#if hasMoreNext}}has-more-next{{/if}} {{hideUsername}} {{hideAvatar}}">
<ul aria-live="polite">
{{#if canPreview}}
{{#if hasMore}}
<li class="load-more">
{{#if isLoading}}
{{> loading}}
{{/if}}
</li>
{{else}}
<li class="start color-info-font-color">
{{#if hasPurge}}
<div class="start__purge-warning error-background error-border error-color">
{{> icon block="start__purge-warning-icon" icon="warning"}}
{{#unless filesOnly}}
{{#unless excludePinned}}
{{_ "RetentionPolicy_RoomWarning" time=purgeTimeout}}
{{else}}
{{_ "RetentionPolicy_RoomWarning_Unpinned" time=purgeTimeout}}
{{/unless}}
{{else}}
{{#unless excludePinned}}
{{_ "RetentionPolicy_RoomWarning_FilesOnly" time=purgeTimeout}}
{{else}}
{{_ "RetentionPolicy_RoomWarning_UnpinnedFilesOnly" time=purgeTimeout}}
{{/unless}}
{{/unless}}
</div>
{{/if}}
{{_ "Start_of_conversation"}}
</li>
{{/if}}
{{/if}}
{{# with messageContext}}
{{#each msg in messagesHistory}}{{#nrr nrrargs 'message' msg=msg room=room subscription=subscription settings=settings u=u}}{{/nrr}}{{/each}}
{{/with}}
{{#if hasMoreNext}}
</div>
{{/unless}}
{{#with roomLeader}}
<div class="room-leader message color-primary-font-color content-background-color border-component-color {{hideLeaderHeader}}">
<button class="thumb user-card-message">
{{> avatar username=username }}
</button>
<div class="leader-name">{{name}}</div>
<div class="leader-status userStatus">
<span class="color-ball status-bg-{{status}}"></span>
<span class="status-text leader-status-text">{{_ statusDisplay}}</span>
</div>
<a class="chat-now" href="{{chatNowLink}}">{{_ "Chat_Now"}}</a>
</div>
{{/with}}
<div class="wrapper {{#if hasMoreNext}}has-more-next{{/if}} {{hideUsername}} {{hideAvatar}}">
<ul aria-live="polite">
{{#if canPreview}}
{{#if hasMore}}
<li class="load-more">
{{#if isLoading}}
{{> loading}}
{{/if}}
</li>
{{else}}
<li class="start color-info-font-color">
{{#if hasPurge}}
<div class="start__purge-warning error-background error-border error-color">
{{> icon block="start__purge-warning-icon" icon="warning"}}
{{#unless filesOnly}}
{{#unless excludePinned}}
{{_ "RetentionPolicy_RoomWarning" time=purgeTimeout}}
{{else}}
{{_ "RetentionPolicy_RoomWarning_Unpinned" time=purgeTimeout}}
{{/unless}}
{{else}}
{{#unless excludePinned}}
{{_ "RetentionPolicy_RoomWarning_FilesOnly" time=purgeTimeout}}
{{else}}
{{_ "RetentionPolicy_RoomWarning_UnpinnedFilesOnly" time=purgeTimeout}}
{{/unless}}
{{/unless}}
</div>
{{/if}}
{{_ "Start_of_conversation"}}
</li>
{{/if}}
</ul>
</div>
{{/if}}
{{# with messageContext}}
{{#each msg in messagesHistory}}{{#nrr nrrargs 'message' msg=msg room=room subscription=subscription settings=settings u=u}}{{/nrr}}{{/each}}
{{/with}}
{{#if hasMoreNext}}
<li class="load-more">
{{#if isLoading}}
{{> loading}}
{{/if}}
</li>
{{/if}}
</ul>
</div>
<footer class="footer border-component-color">
{{> messageBox messageboxData}}
</footer>
</div>
{{#with flexData}}
{{> contextualBar}}
{{/with}}
<footer class="footer border-component-color">
{{> messageBox messageboxData}}
</footer>
</div>
</section>
</div>
{{#with flexData}}
{{> contextualBar}}
{{/with}}
</div>
</section>
</div>
</template>

@ -1,3 +1,8 @@
import _ from 'underscore';
import moment from 'moment';
import mime from 'mime-type/with-db';
import Clipboard from 'clipboard';
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Random } from 'meteor/random';
@ -5,6 +10,7 @@ import { Blaze } from 'meteor/blaze';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { t, roomTypes, getUserPreference, handleError } from '../../../../utils';
import { WebRTC } from '../../../../webrtc/client';
import { ChatMessage, RoomRoles, Users, Subscriptions, Rooms } from '../../../../models';
@ -26,10 +32,6 @@ import { settings } from '../../../../settings';
import { callbacks } from '../../../../callbacks';
import { promises } from '../../../../promises/client';
import { hasAllPermission, hasRole } from '../../../../authorization';
import _ from 'underscore';
import moment from 'moment';
import mime from 'mime-type/with-db';
import Clipboard from 'clipboard';
import { lazyloadtick } from '../../../../lazy-load';
import { ChatMessages } from '../../lib/chatMessages';
import { fileUpload } from '../../lib/fileUpload';
@ -520,6 +522,52 @@ let lastTouchX = null;
let lastTouchY = null;
let lastScrollTop;
export const dropzoneEvents = {
'dragenter .dropzone'(e) {
const types = e.originalEvent && e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.types;
if (types != null && types.length > 0 && _.every(types, (type) => type.indexOf('text/') === -1 || type.indexOf('text/uri-list') !== -1) && userCanDrop(this._id)) {
e.currentTarget.classList.add('over');
}
e.stopPropagation();
},
'dragleave .dropzone-overlay'(e) {
e.currentTarget.parentNode.classList.remove('over');
e.stopPropagation();
},
'dragover .dropzone-overlay'(e) {
e = e.originalEvent || e;
if (['move', 'linkMove'].includes(e.dataTransfer.effectAllowed)) {
e.dataTransfer.dropEffect = 'move';
} else {
e.dataTransfer.dropEffect = 'copy';
}
e.stopPropagation();
},
'dropped .dropzone-overlay'(event, instance) {
event.currentTarget.parentNode.classList.remove('over');
const e = event.originalEvent || event;
e.stopPropagation();
const files = (e.dataTransfer != null ? e.dataTransfer.files : undefined) || [];
const filesToUpload = Array.from(files).map((file) => {
Object.defineProperty(file, 'type', { value: mime.lookup(file.name) });
return {
file,
name: file.name,
};
});
return instance.onFile && instance.onFile(filesToUpload);
},
};
Template.room.events({
'click .js-open-thread'(event) {
event.preventDefault();
@ -809,45 +857,6 @@ Template.room.events({
ChatMessage.update({ _id }, { $set: { collapsed: !collapsed } });
},
'dragenter .dropzone'(e) {
const types = e.originalEvent && e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.types;
if (types != null && types.length > 0 && _.every(types, (type) => type.indexOf('text/') === -1 || type.indexOf('text/uri-list') !== -1) && userCanDrop(this._id)) {
e.currentTarget.classList.add('over');
}
},
'dragleave .dropzone-overlay'(e) {
e.currentTarget.parentNode.classList.remove('over');
},
'dragover .dropzone-overlay'(e) {
e = e.originalEvent || e;
if (['move', 'linkMove'].includes(e.dataTransfer.effectAllowed)) {
e.dataTransfer.dropEffect = 'move';
} else {
e.dataTransfer.dropEffect = 'copy';
}
},
'dropped .dropzone-overlay'(event) {
event.currentTarget.parentNode.classList.remove('over');
const e = event.originalEvent || event;
const files = (e.dataTransfer != null ? e.dataTransfer.files : undefined) || [];
const filesToUpload = [];
for (const file of Array.from(files)) {
// `file.type = mime.lookup(file.name)` does not work.
Object.defineProperty(file, 'type', { value: mime.lookup(file.name) });
filesToUpload.push({
file,
name: file.name,
});
}
fileUpload(filesToUpload, chatMessages[RoomManager.openedRoom].input, { rid: RoomManager.openedRoom });
},
'load img'(e, template) {
return (typeof template.sendToBottomIfNecessary === 'function' ? template.sendToBottomIfNecessary() : undefined);
},
@ -944,6 +953,11 @@ Template.room.onCreated(function() {
// this.typing = new msgTyping this.data._id
lazyloadtick();
const rid = this.data._id;
this.onFile = (filesToUpload) => {
fileUpload(filesToUpload, chatMessages[rid].input, { rid });
};
this.rid = rid;
this.subscription = Subscriptions.findOne({ rid });
this.room = Rooms.findOne({ _id: rid });

Loading…
Cancel
Save