diff --git a/grunt_config.json b/grunt_config.json index 260c6a11..9c0ccd44 100644 --- a/grunt_config.json +++ b/grunt_config.json @@ -24,6 +24,10 @@ "src": "<%= dirs.css.src %>/style-polymer.css", "dest": "<%= dirs.js.dest %>/src/components/style-polymer.css" }, + { + "src": "<%= dirs.js.node_modules %>/filepond/dist/filepond.css", + "dest": "<%= dirs.css.dest %>/filepond.css" + }, { "src": "<%= dirs.js.src %>/select2/select2.css", "dest": "<%= dirs.css.dest %>/select2.css" @@ -39,12 +43,12 @@ "<%= dirs.js.node_modules %>/luxon/build/global/luxon.min.js", "<%= dirs.js.node_modules %>/clipboard/dist/clipboard.min.js", "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js", - "<%= dirs.js.node_modules %>/dropzone/dist/min/dropzone.min.js", "<%= dirs.js.node_modules %>/sweetalert2/dist/sweetalert2.min.js", "<%= dirs.js.node_modules %>/sticky-sidebar/dist/sticky-sidebar.min.js", "<%= dirs.js.node_modules %>/sticky-sidebar/dist/jquery.sticky-sidebar.min.js", "<%= dirs.js.node_modules %>/waypoints/lib/noframework.waypoints.min.js", "<%= dirs.js.node_modules %>/waypoints/lib/jquery.waypoints.min.js", + "<%= dirs.js.node_modules %>/filepond/dist/filepond.min.js", "<%= dirs.js.src %>/logging.js", "<%= dirs.js.src %>/bootstrap.js", "<%= dirs.js.src %>/i18n_utils.js", diff --git a/package-lock.json b/package-lock.json index 9838d53e..9f79e30b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,9 +26,9 @@ "clipboard": "^2.0.1", "copy-webpack-plugin": "^4.4.2", "css-loader": "^0.28.11", - "dropzone": "^5.5.0", "exports-loader": "^0.6.4", "favico.js": "^0.3.10", + "filepond": "^4.32.10", "grunt": "^0.4.5", "grunt-cli": "^1.4.3", "grunt-contrib-concat": "^0.5.1", @@ -51,7 +51,7 @@ "raw-loader": "1.0.0-beta.0", "sticky-sidebar": "3.3.1", "style-loader": "^0.21.0", - "sweetalert2": "^9.10.12", + "sweetalert2": "^11.26.3", "ts-loader": "^1.3.3", "waypoints": "4.0.1", "webpack": "4.23.1", @@ -3284,12 +3284,6 @@ "domelementtype": "1" } }, - "node_modules/dropzone": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", - "integrity": "sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA==", - "dev": true - }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -3916,6 +3910,12 @@ "dev": true, "optional": true }, + "node_modules/filepond": { + "version": "4.32.10", + "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.32.10.tgz", + "integrity": "sha512-uflpIL+rfAgsYk7HgXugh7ELlHNHLTPtIHfvuq0gv3x2qNfVxdGA6T7RPxMh9Vji8ysXstXkDHVXQ4hY38MPTQ==", + "dev": true + }, "node_modules/fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -10334,12 +10334,13 @@ } }, "node_modules/sweetalert2": { - "version": "9.17.4", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-9.17.4.tgz", - "integrity": "sha512-T6Js/kjtGmpekE1tPT+drdgsedEnaet03mMadKUMvtBlFzVLcYy8kvIuchaQJ09rhG5bdmRRMQ2sEK0ZnHi1vg==", + "version": "11.26.3", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.3.tgz", + "integrity": "sha512-VU0hGw/WfI9h7Mh+SCsDlWgtxDwWZ6ccqS7QcO8zEeWnwplN1GptcLstq76OluUBSLUza6ldvKd3558OhjpJ9A==", "dev": true, "funding": { - "url": "https://sweetalert2.github.io/#donations" + "type": "individual", + "url": "https://github.com/sponsors/limonte" } }, "node_modules/tapable": { @@ -14789,12 +14790,6 @@ "domelementtype": "1" } }, - "dropzone": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", - "integrity": "sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA==", - "dev": true - }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -15324,6 +15319,12 @@ "dev": true, "optional": true }, + "filepond": { + "version": "4.32.10", + "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.32.10.tgz", + "integrity": "sha512-uflpIL+rfAgsYk7HgXugh7ELlHNHLTPtIHfvuq0gv3x2qNfVxdGA6T7RPxMh9Vji8ysXstXkDHVXQ4hY38MPTQ==", + "dev": true + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -20525,9 +20526,9 @@ } }, "sweetalert2": { - "version": "9.17.4", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-9.17.4.tgz", - "integrity": "sha512-T6Js/kjtGmpekE1tPT+drdgsedEnaet03mMadKUMvtBlFzVLcYy8kvIuchaQJ09rhG5bdmRRMQ2sEK0ZnHi1vg==", + "version": "11.26.3", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.3.tgz", + "integrity": "sha512-VU0hGw/WfI9h7Mh+SCsDlWgtxDwWZ6ccqS7QcO8zEeWnwplN1GptcLstq76OluUBSLUza6ldvKd3558OhjpJ9A==", "dev": true }, "tapable": { diff --git a/package.json b/package.json index 85240e78..7904667d 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,9 @@ "clipboard": "^2.0.1", "copy-webpack-plugin": "^4.4.2", "css-loader": "^0.28.11", - "dropzone": "^5.5.0", "exports-loader": "^0.6.4", "favico.js": "^0.3.10", + "filepond": "^4.32.10", "grunt": "^0.4.5", "grunt-cli": "^1.4.3", "grunt-contrib-concat": "^0.5.1", @@ -51,7 +51,7 @@ "raw-loader": "1.0.0-beta.0", "sticky-sidebar": "3.3.1", "style-loader": "^0.21.0", - "sweetalert2": "^9.10.12", + "sweetalert2": "^11.26.3", "ts-loader": "^1.3.3", "waypoints": "4.0.1", "webpack": "4.23.1", diff --git a/rhodecode/apps/repository/__init__.py b/rhodecode/apps/repository/__init__.py index 82538c6d..5bb15c71 100644 --- a/rhodecode/apps/repository/__init__.py +++ b/rhodecode/apps/repository/__init__.py @@ -203,7 +203,6 @@ def includeme(config): route_name="repo_commit_comment_attachment_upload", request_method="POST", renderer="json_ext", - xhr=True, ) config.add_route( diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py index 23cefd7f..d6b5e087 100644 --- a/rhodecode/apps/repository/views/repo_commits.py +++ b/rhodecode/apps/repository/views/repo_commits.py @@ -588,7 +588,7 @@ class RepoCommitsView(RepoAppView): ".xlsx", ".zip", ] - max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js + max_file_size = 10 * 1024 * 1024 # 10MB try: f_store = store_utils.get_filestore_backend(self.request.registry.settings) diff --git a/rhodecode/apps/repository/views/repo_files.py b/rhodecode/apps/repository/views/repo_files.py index 8505fabb..76178e35 100644 --- a/rhodecode/apps/repository/views/repo_files.py +++ b/rhodecode/apps/repository/views/repo_files.py @@ -1453,6 +1453,10 @@ class RepoFilesView(RepoAppView): nodes = {} for file_obj in r_post.getall("files_upload") or []: + if file_obj == "{}": + # filepond sends the field with the same name as the file field with metadata which in our case is empty + continue + content = file_obj.file filename = file_obj.filename diff --git a/rhodecode/public/css/filepond.less b/rhodecode/public/css/filepond.less new file mode 100644 index 00000000..104f6e9b --- /dev/null +++ b/rhodecode/public/css/filepond.less @@ -0,0 +1,23 @@ +.filepond--root .filepond--file-info, +.filepond--root .filepond--file-info-main, +.filepond--root .filepond--file-info-sub { + pointer-events: auto !important; +} + +/* Make the “sub” line look like the main line (brighter) */ +.filepond--root .filepond--file-info .filepond--file-info-sub { + opacity: 1 !important; /* remove faded look */ + color: #ffffff; /* or whatever contrasts your green */ + font-size: 0.75em; /* same as main, if you like */ +} + +/* Style the link itself */ +.filepond--root .filepond--file-info .filepond--file-info-sub a { + color: #ffffff; + text-decoration: underline; + cursor: pointer; +} + +.filepond--root .filepond--drop-label .filepond--label-action { + font-family: @text-regular; /* or a different font */ +} diff --git a/rhodecode/public/css/main.less b/rhodecode/public/css/main.less index fab77fea..1dcbead1 100644 --- a/rhodecode/public/css/main.less +++ b/rhodecode/public/css/main.less @@ -28,6 +28,7 @@ @import 'deform'; @import 'tooltips'; @import 'sweetalert2'; +@import 'filepond'; //--- BASE ------------------// @@ -2957,27 +2958,11 @@ form.markup-form { } -.dropzone-wrapper { +.filepond-wrapper { border: 1px solid @grey5; padding: 20px; } -.dropzone, -.dropzone-pure { - border: 2px dashed @grey5; - border-radius: 5px; - background: white; - min-height: 200px; - padding: 54px; - - .dz-message { - font-weight: 700; - text-align: center; - margin: 2em 0; - } - -} - .dz-preview { margin: 10px 0 !important; position: relative; diff --git a/rhodecode/public/js/src/plugins/jquery.timeago-extension.js b/rhodecode/public/js/src/plugins/jquery.timeago-extension.js index ab314d66..ecf7227b 100644 --- a/rhodecode/public/js/src/plugins/jquery.timeago-extension.js +++ b/rhodecode/public/js/src/plugins/jquery.timeago-extension.js @@ -36,7 +36,7 @@ var AgeModule = (function () { day: days, hour: hours, minute: minutes, - second: seconds, + second: Math.round(seconds), }; }; diff --git a/rhodecode/public/js/src/rhodecode/comments.js b/rhodecode/public/js/src/rhodecode/comments.js index 5150d3b5..fd256e2d 100644 --- a/rhodecode/public/js/src/rhodecode/comments.js +++ b/rhodecode/public/js/src/rhodecode/comments.js @@ -928,7 +928,6 @@ var CommentsController = function() { }, 100); } - // add dropzone support var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) { var renderer = templateContext.visual.default_renderer; if (renderer == 'rst') { @@ -949,74 +948,115 @@ var CommentsController = function() { return false; }; - //see: https://www.dropzonejs.com/#configuration - var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload', + let storeUrl = pyroutes.url('repo_commit_comment_attachment_upload', {'repo_name': templateContext.repo_name, 'commit_id': templateContext.commit_data.commit_id}) - var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0); + let previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0); if (previewTmpl !== undefined){ - var selectLink = $(formElement).find('.pick-attachment').get(0); - $(formElement).find('.comment-attachment-uploader').dropzone({ - url: storeUrl, - headers: {"X-CSRF-Token": CSRF_TOKEN}, - paramName: function () { - return "attachment" - }, // The name that will be used to transfer the file - clickable: selectLink, - parallelUploads: 1, - maxFiles: 10, - maxFilesize: templateContext.attachment_store.max_file_size_mb, - uploadMultiple: false, - autoProcessQueue: true, // if false queue will not be processed automatically. - createImageThumbnails: false, - previewTemplate: previewTmpl.innerHTML, + let $uploader = $(formElement).find('.comment-attachment-uploader'); + let inputElement = $uploader.find('.comment-attachment-input')[0]; + let selectLink = $(formElement).find('.pick-attachment').get(0); - accept: function (file, done) { - done(); + let pond = FilePond.create(inputElement, { + name: 'attachment', + credits: false, + allowMultiple: true, + allowRevert: false, + maxFiles: 10, + maxParallelUploads: 1, + maxFileSize: templateContext.attachment_store.max_file_size_mb + 'MB', + + // custom upload so we can send CSRF header etc + server: { + process: function (fieldName, file, metadata, load, error, progress, abort) { + let xhr = new XMLHttpRequest(); + xhr.open('POST', storeUrl); + + xhr.setRequestHeader('X-CSRF-Token', CSRF_TOKEN); + + let formData = new FormData(); + formData.append('attachment', file, file.name); + + xhr.upload.onprogress = function (e) { + if (e.lengthComputable) { + progress(e.loaded, e.loaded, e.total); + } + }; + + xhr.onreadystatechange = function () { + if (xhr.readyState !== XMLHttpRequest.DONE) { + return; + } + + pond.removeFile(file.id); //removes status + + if (xhr.status >= 200 && xhr.status < 300) { + load(xhr.responseText); + } else { + const parsed_resp = JSON.parse(xhr.responseText || '{}'); + if (parsed_resp.error === '') return; + + const message = (parsed_resp.error || parsed_resp.main || parsed_resp.message || "Unknown error"); + + $.Topic('/notifications').publish({ + message: { + message: message, + level: "error", + force: true + } + }); + } + }; + + xhr.send(formData); + + return { + abort: function () { + xhr.abort(); + abort(); + } + }; }, - init: function () { + }, - this.on("sending", function (file, xhr, formData) { - $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide(); - $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show(); - }); + // get rid of "tap to undo" etc + labelTapToUndo: '', + labelTapToRetry: '', + labelTapToCancel: '' + }); - this.on("success", function (file, response) { - $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show(); - $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide(); - - var isRendered = false; - var ext = file.name.split('.').pop(); - var imageExts = templateContext.attachment_store.image_ext; - if (imageExts.indexOf(ext) !== -1){ - isRendered = true; - } - - insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered) - }); - - this.on("error", function (file, errorMessage, xhr) { - $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide(); - - var error = null; - - if (xhr !== undefined){ - var httpStatus = xhr.status + " " + xhr.statusText; - if (xhr !== undefined && xhr.status >= 500) { - error = httpStatus; - } - } - - if (error === null) { - error = errorMessage.error || errorMessage || httpStatus; - } - $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error)); - - }); - } + if (selectLink) { + $(selectLink).off('click').on('click', function (e) { + e.preventDefault(); + pond.browse(); }); } + + pond.on('processfile', function (errorObj, file) { + if (errorObj) return; + + // success: parse server response + let response; + try { + response = JSON.parse(file.serverId || '{}'); + } catch (e) { + response = null; + } + if (!response) { + return; + } + + let filename = file.filename || (file.file && file.file.name) || file.name || ''; + let ext = filename.split('.').pop(); + let imageExts = templateContext.attachment_store.image_ext || []; + let isRendered = imageExts.indexOf(ext) !== -1; + + pond.removeFile(file.id); + + insertAttachmentText(cm, filename, response.repo_fqn_access_path, isRendered); + }); + } return commentForm; }; @@ -1318,6 +1358,7 @@ var CommentsController = function() { }; this.createComment = function(node, f_path, line_no, resolutionComment) { + self.edit = false; var $node = $(node); var $td = $node.closest('td'); diff --git a/rhodecode/templates/base/root.mako b/rhodecode/templates/base/root.mako index 3f6fbd7c..5ef599fc 100644 --- a/rhodecode/templates/base/root.mako +++ b/rhodecode/templates/base/root.mako @@ -63,8 +63,9 @@ c.template_context['attachment_store'] = { ## CSS definitions <%def name="css()"> - + + ## EXTRA FOR CSS ${self.css_extra()} %def> diff --git a/rhodecode/templates/changeset/changeset_file_comment.mako b/rhodecode/templates/changeset/changeset_file_comment.mako index df9cadfd..d4b84ea1 100644 --- a/rhodecode/templates/changeset/changeset_file_comment.mako +++ b/rhodecode/templates/changeset/changeset_file_comment.mako @@ -468,21 +468,14 @@
-