From 73443d889cd36a8488657a6a45111f5789d34b8b Mon Sep 17 00:00:00 2001 From: Maja Komel Date: Wed, 3 Oct 2018 03:12:36 +0200 Subject: [PATCH] FIX: keep files in order when adding multiple uploads (#6306) * FIX: keep files in order when adding multiple uploads * use filename in the placeholder when uploading files * add tests * add consecutive nr to the placeholder when multiple uploads with the same filename --- .../components/composer-editor.js.es6 | 109 ++++++++++++++---- config/locales/client.en.yml | 3 +- .../acceptance/composer-test.js.es6 | 74 ++++++++++++ 3 files changed, 161 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 6218dbcfbc2..344da0f340a 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -53,10 +53,15 @@ export default Ember.Component.extend({ _xhr: null, shouldBuildScrollMap: true, scrollMap: null, + uploadFilenamePlaceholder: null, - @computed - uploadPlaceholder() { - return `[${I18n.t("uploading")}]() `; + @computed("uploadFilenamePlaceholder") + uploadPlaceholder(uploadFilenamePlaceholder) { + const clipboard = I18n.t("clipboard"); + const filename = uploadFilenamePlaceholder + ? uploadFilenamePlaceholder + : clipboard; + return `[${I18n.t("uploading_filename", { filename })}]() `; }, @computed("composer.requiredCategoryMissing") @@ -218,6 +223,53 @@ export default Ember.Component.extend({ } }, + _setUploadPlaceholderSend(data) { + const filename = this._filenamePlaceholder(data); + this.set("uploadFilenamePlaceholder", filename); + + // when adding two separate files with the same filename search for matching + // placeholder already existing in the editor ie [Uploading: test.png...] + // and add order nr to the next one: [Uplodading: test.png(1)...] + const regexString = `\\[${I18n.t("uploading_filename", { + filename: filename + "(?:\\()?([0-9])?(?:\\))?" + })}\\]\\(\\)`; + const globalRegex = new RegExp(regexString, "g"); + const matchingPlaceholder = this.get("composer.reply").match(globalRegex); + if (matchingPlaceholder) { + // get last matching placeholder and its consecutive nr in regex + // capturing group and apply +1 to the placeholder + const lastMatch = matchingPlaceholder[matchingPlaceholder.length - 1]; + const regex = new RegExp(regexString); + const orderNr = regex.exec(lastMatch)[1] + ? parseInt(regex.exec(lastMatch)[1]) + 1 + : 1; + data.orderNr = orderNr; + const filenameWithOrderNr = `${filename}(${orderNr})`; + this.set("uploadFilenamePlaceholder", filenameWithOrderNr); + } + }, + + _setUploadPlaceholderDone(data) { + const filename = this._filenamePlaceholder(data); + const filenameWithSize = `${filename} (${data.total})`; + this.set("uploadFilenamePlaceholder", filenameWithSize); + + if (data.orderNr) { + const filenameWithOrderNr = `${filename}(${data.orderNr})`; + this.set("uploadFilenamePlaceholder", filenameWithOrderNr); + } else { + this.set("uploadFilenamePlaceholder", filename); + } + }, + + _filenamePlaceholder(data) { + return data.files[0].name.replace(/\u200B-\u200D\uFEFF]/g, ""); + }, + + _resetUploadFilenamePlaceholder() { + this.set("uploadFilenamePlaceholder", null); + }, + _enableAdvancedEditorPreviewSync() { return this.siteSettings.enable_advanced_editor_preview_sync; }, @@ -542,23 +594,26 @@ export default Ember.Component.extend({ }, _resetUpload(removePlaceholder) { - if (this._validUploads > 0) { - this._validUploads--; - } - if (this._validUploads === 0) { - this.setProperties({ - uploadProgress: 0, - isUploading: false, - isCancellable: false - }); - } - if (removePlaceholder) { - this.appEvents.trigger( - "composer:replace-text", - this.get("uploadPlaceholder"), - "" - ); - } + Ember.run.next(() => { + if (this._validUploads > 0) { + this._validUploads--; + } + if (this._validUploads === 0) { + this.setProperties({ + uploadProgress: 0, + isUploading: false, + isCancellable: false + }); + } + if (removePlaceholder) { + this.appEvents.trigger( + "composer:replace-text", + this.get("uploadPlaceholder"), + "" + ); + } + this._resetUploadFilenamePlaceholder(); + }); }, _bindUploadTarget() { @@ -568,7 +623,6 @@ export default Ember.Component.extend({ const $element = this.$(); const csrf = this.session.get("csrfToken"); - const uploadPlaceholder = this.get("uploadPlaceholder"); $element.fileupload({ url: Discourse.getURL( @@ -637,7 +691,13 @@ export default Ember.Component.extend({ $element.on("fileuploadsend", (e, data) => { this._pasted = false; this._validUploads++; - this.appEvents.trigger("composer:insert-text", uploadPlaceholder); + + this._setUploadPlaceholderSend(data); + + this.appEvents.trigger( + "composer:insert-text", + this.get("uploadPlaceholder") + ); if (data.xhr && data.originalFiles.length === 1) { this.set("isCancellable", true); @@ -647,13 +707,13 @@ export default Ember.Component.extend({ $element.on("fileuploaddone", (e, data) => { let upload = data.result; - + this._setUploadPlaceholderDone(data); if (!this._xhr || !this._xhr._userCancelled) { const markdown = getUploadMarkdown(upload); cacheShortUploadUrl(upload.short_url, upload.url); this.appEvents.trigger( "composer:replace-text", - uploadPlaceholder.trim(), + this.get("uploadPlaceholder").trim(), markdown ); this._resetUpload(false); @@ -663,6 +723,7 @@ export default Ember.Component.extend({ }); $element.on("fileuploadfail", (e, data) => { + this._setUploadPlaceholderDone(data); this._resetUpload(true); const userCancelled = this._xhr && this._xhr._userCancelled; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index dbcd4795f68..d2191844a70 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -317,7 +317,8 @@ en: upload: "Upload" uploading: "Uploading..." - uploading_filename: "Uploading {{filename}}..." + uploading_filename: "Uploading: {{filename}}..." + clipboard: "clipboard" uploaded: "Uploaded!" pasting: "Pasting..." diff --git a/test/javascripts/acceptance/composer-test.js.es6 b/test/javascripts/acceptance/composer-test.js.es6 index b73e9f6c62e..4529379c85f 100644 --- a/test/javascripts/acceptance/composer-test.js.es6 +++ b/test/javascripts/acceptance/composer-test.js.es6 @@ -89,6 +89,80 @@ QUnit.test("Tests the Composer controls", async assert => { assert.ok(!exists(".bootbox.modal"), "the confirmation can be cancelled"); }); +QUnit.test("Composer upload placeholder", async assert => { + await visit("/"); + await click("#create-topic"); + + const file1 = new Blob([""], { type: "image/png" }); + file1.name = "test.png"; + const data1 = { + files: [file1], + result: { + original_filename: "test.png", + thumbnail_width: 200, + thumbnail_height: 300, + url: "/uploads/test1.ext" + } + }; + + const file2 = new Blob([""], { type: "image/png" }); + file2.name = "test.png"; + const data2 = { + files: [file2], + result: { + original_filename: "test.png", + thumbnail_width: 100, + thumbnail_height: 200, + url: "/uploads/test2.ext" + } + }; + + const file3 = new Blob([""], { type: "image/png" }); + file3.name = "image.png"; + const data3 = { + files: [file3], + result: { + original_filename: "image.png", + thumbnail_width: 300, + thumbnail_height: 400, + url: "/uploads/test3.ext" + } + }; + + await find(".wmd-controls").trigger("fileuploadsend", data1); + assert.equal(find(".d-editor-input").val(), "[Uploading: test.png...]() "); + + await find(".wmd-controls").trigger("fileuploadsend", data2); + assert.equal( + find(".d-editor-input").val(), + "[Uploading: test.png...]() [Uploading: test.png(1)...]() " + ); + + await find(".wmd-controls").trigger("fileuploadsend", data3); + assert.equal( + find(".d-editor-input").val(), + "[Uploading: test.png...]() [Uploading: test.png(1)...]() [Uploading: image.png...]() " + ); + + await find(".wmd-controls").trigger("fileuploaddone", data2); + assert.equal( + find(".d-editor-input").val(), + "[Uploading: test.png...]() ![test|100x200](/uploads/test2.ext) [Uploading: image.png...]() " + ); + + await find(".wmd-controls").trigger("fileuploaddone", data3); + assert.equal( + find(".d-editor-input").val(), + "[Uploading: test.png...]() ![test|100x200](/uploads/test2.ext) ![image|300x400](/uploads/test3.ext) " + ); + + await find(".wmd-controls").trigger("fileuploaddone", data1); + assert.equal( + find(".d-editor-input").val(), + "![test|200x300](/uploads/test1.ext) ![test|100x200](/uploads/test2.ext) ![image|300x400](/uploads/test3.ext) " + ); +}); + QUnit.test("Create a topic with server side errors", async assert => { await visit("/"); await click("#create-topic");