diff --git a/public/less/generics.less b/public/less/generics.less index d7cd2b3ec3..ec8b0c1adf 100644 --- a/public/less/generics.less +++ b/public/less/generics.less @@ -172,4 +172,13 @@ .timeline-badge { padding: 1rem; } +} + +.imagedrop { + position: absolute; + text-align: center; + font-size: 24px; + color: @gray-light; + width: 100%; + display: none; } \ No newline at end of file diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 10e9ec4b52..1c27176959 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -14,11 +14,12 @@ define('forum/chats', [ 'alerts', 'chat', 'api', + 'uploadHelpers', ], function ( components, translator, mousetrap, recentChats, search, messages, autocomplete, hooks, bootbox, alerts, chatModule, - api + api, uploadHelpers ) { const Chats = { initialised: false, @@ -69,12 +70,35 @@ define('forum/chats', [ Chats.addCharactersLeftHandler($('[component="chat/main-wrapper"]')); Chats.addIPHandler($('[component="chat/main-wrapper"]')); Chats.createAutoComplete($('[component="chat/input"]')); + Chats.addUploadHandler({ + dragDropAreaEl: $('.chats-full'), + pasteEl: $('[component="chat/input"]'), + uploadFormEl: $('[component="chat/upload"]'), + inputEl: $('[component="chat/input"]'), + }); $('[data-action="close"]').on('click', function () { Chats.switchChat(); }); }; + Chats.addUploadHandler = function (options) { + uploadHelpers.init({ + dragDropAreaEl: options.dragDropAreaEl, + pasteEl: options.pasteEl, + uploadFormEl: options.uploadFormEl, + route: '/api/post/upload', // using same route as post uploads + callback: function (uploads) { + const inputEl = options.inputEl; + let text = inputEl.val(); + uploads.forEach((upload) => { + text = text + (text ? '\n' : '') + (upload.isImage ? '!' : '') + `[${upload.filename}](${upload.url})`; + }); + inputEl.val(text); + }, + }); + }; + Chats.addIPHandler = function (container) { container.on('click', '.chat-ip-button', function () { const ipEl = $(this).parent(); diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index da0fc006e7..a5fbbc8a8d 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -89,8 +89,8 @@ define('alerts', ['translator', 'components', 'hooks'], function (translator, co } function updateAlert(alert, params) { - alert.find('strong').html(params.title); - alert.find('p').html(params.message); + alert.find('strong').translateHtml(params.title); + alert.find('p').translateHtml(params.message); alert.attr('class', 'alert alert-dismissable alert-' + params.type + ' clearfix'); clearTimeout(parseInt(alert.attr('timeoutId'), 10)); @@ -98,12 +98,7 @@ define('alerts', ['translator', 'components', 'hooks'], function (translator, co startTimeout(alert, params); } - alert.children().fadeOut(100); - translator.translate(alert.html(), function (translatedHTML) { - alert.children().fadeIn(100); - alert.html(translatedHTML); - hooks.fire('action:alert.update', { alert, params }); - }); + hooks.fire('action:alert.update', { alert, params }); // Handle changes in the clickfn alert.off('click').removeClass('pointer'); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 78b1e67902..80f22a9b24 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -196,7 +196,9 @@ define('chat', [ module.createModal = function (data, callback) { callback = callback || function () {}; - require(['scrollStop', 'forum/chats', 'forum/chats/messages'], function (scrollStop, Chats, ChatsMessages) { + require([ + 'scrollStop', 'forum/chats', 'forum/chats/messages', + ], function (scrollStop, Chats, ChatsMessages) { app.parseAndTranslate('chat', data, function (chatModal) { if (module.modalExists(data.roomId)) { return callback(module.getModal(data.roomId)); @@ -233,7 +235,7 @@ define('chat', [ taskbar.updateActive(uuid); }, stop: function () { - chatModal.find('#chat-message-input').focus(); + module.focusInput(chatModal); }, distance: 10, handle: '.modal-header', @@ -297,6 +299,14 @@ define('chat', [ Chats.addCharactersLeftHandler(chatModal); Chats.addIPHandler(chatModal); + + Chats.addUploadHandler({ + dragDropAreaEl: chatModal.find('.modal-content'), + pasteEl: chatModal, + uploadFormEl: chatModal.find('[component="chat/upload"]'), + inputEl: chatModal.find('[component="chat/input"]'), + }); + ChatsMessages.addSocketListeners(); taskbar.push('chat', chatModal.attr('data-uuid'), { @@ -318,7 +328,9 @@ define('chat', [ }; module.focusInput = function (chatModal) { - chatModal.find('[component="chat/input"]').focus(); + setTimeout(function () { + chatModal.find('[component="chat/input"]').focus(); + }, 20); }; module.close = function (chatModal) { diff --git a/public/src/modules/uploadHelpers.js b/public/src/modules/uploadHelpers.js new file mode 100644 index 0000000000..9914dc0a3d --- /dev/null +++ b/public/src/modules/uploadHelpers.js @@ -0,0 +1,199 @@ +'use strict'; + + +define('uploadHelpers', ['alerts'], function (alerts) { + const uploadHelpers = {}; + + uploadHelpers.init = function (options) { + const formEl = options.uploadFormEl; + if (!formEl.length) { + return; + } + formEl.attr('action', config.relative_path + options.route); + + if (options.dragDropAreaEl) { + uploadHelpers.handleDragDrop({ + container: options.dragDropAreaEl, + callback: function (upload) { + uploadHelpers.ajaxSubmit({ + uploadForm: formEl, + upload: upload, + callback: options.callback, + }); + }, + }); + } + + if (options.pasteEl) { + uploadHelpers.handlePaste({ + container: options.pasteEl, + callback: function (upload) { + uploadHelpers.ajaxSubmit({ + uploadForm: formEl, + upload: upload, + callback: options.callback, + }); + }, + }); + } + }; + + uploadHelpers.handleDragDrop = function (options) { + let draggingDocument = false; + const postContainer = options.container; + const drop = options.container.find('.imagedrop'); + + postContainer.on('dragenter', function onDragEnter() { + if (draggingDocument) { + return; + } + drop.css('top', '0px'); + drop.css('height', postContainer.height() + 'px'); + drop.css('line-height', postContainer.height() + 'px'); + drop.show(); + + drop.on('dragleave', function () { + drop.hide(); + drop.off('dragleave'); + }); + }); + + drop.on('drop', function onDragDrop(e) { + e.preventDefault(); + const files = e.originalEvent.dataTransfer.files; + + if (files.length) { + let formData; + if (window.FormData) { + formData = new FormData(); + for (var i = 0; i < files.length; ++i) { + formData.append('files[]', files[i], files[i].name); + } + } + options.callback({ + files: files, + formData: formData, + }); + } + + drop.hide(); + return false; + }); + + function cancel(e) { + e.preventDefault(); + return false; + } + + $(document) + .off('dragstart') + .on('dragstart', function () { + draggingDocument = true; + }) + .off('dragend') + .on('dragend', function () { + draggingDocument = false; + }); + + drop.on('dragover', cancel); + drop.on('dragenter', cancel); + }; + + uploadHelpers.handlePaste = function (options) { + const container = options.container; + container.on('paste', function (event) { + const items = (event.clipboardData || event.originalEvent.clipboardData || {}).items; + const files = []; + const fileNames = []; + let formData = null; + if (window.FormData) { + formData = new FormData(); + } + [].forEach.call(items, function (item) { + const file = item.getAsFile(); + if (file) { + const fileName = utils.generateUUID() + '-' + file.name; + if (formData) { + formData.append('files[]', file, fileName); + } + files.push(file); + fileNames.push(fileName); + } + }); + + if (files.length) { + options.callback({ + files: files, + fileNames: fileNames, + formData: formData, + }); + } + }); + }; + + uploadHelpers.ajaxSubmit = function (options) { + const files = [...options.upload.files]; + + for (let i = 0; i < files.length; ++i) { + const isImage = files[i].type.match(/image./); + if ((isImage && !app.user.privileges['upload:post:image']) || (!isImage && !app.user.privileges['upload:post:file'])) { + return alerts.error('[[error:no-privileges]]'); + } + if (files[i].size > parseInt(config.maximumFileSize, 10) * 1024) { + options.uploadForm[0].reset(); + return alerts.error('[[error:file-too-big, ' + config.maximumFileSize + ']]'); + } + } + const alert_id = Date.now(); + options.uploadForm.off('submit').on('submit', function () { + $(this).ajaxSubmit({ + headers: { + 'x-csrf-token': config.csrf_token, + }, + resetForm: true, + clearForm: true, + formData: options.upload.formData, + error: function (xhr) { + let errorMsg = (xhr.responseJSON && + (xhr.responseJSON.error || (xhr.responseJSON.status && xhr.responseJSON.status.message))) || + '[[error:parse-error]]'; + + if (xhr && xhr.status === 413) { + errorMsg = xhr.statusText || 'Request Entity Too Large'; + } + alerts.error(errorMsg); + alerts.remove(alert_id); + }, + + uploadProgress: function (event, position, total, percent) { + alerts.alert({ + alert_id: alert_id, + message: '[[modules:composer.uploading, ' + percent + '%]]', + }); + }, + + success: function (res) { + const uploads = res.response.images; + if (uploads && uploads.length) { + for (var i = 0; i < uploads.length; ++i) { + uploads[i].filename = files[i].name; + uploads[i].isImage = /image./.test(files[i].type); + } + } + options.callback(uploads); + }, + + complete: function () { + options.uploadForm[0].reset(); + setTimeout(alerts.remove, 100, alert_id); + }, + }); + + return false; + }); + + options.uploadForm.submit(); + }; + + return uploadHelpers; +});