diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js index a50aee09671..d110191c24c 100644 --- a/app/assets/javascripts/discourse/app/components/composer-editor.js +++ b/app/assets/javascripts/discourse/app/components/composer-editor.js @@ -73,7 +73,13 @@ export default Component.extend({ const filename = uploadFilenamePlaceholder ? uploadFilenamePlaceholder : clipboard; - return `[${I18n.t("uploading_filename", { filename })}]() `; + + let placeholder = `[${I18n.t("uploading_filename", { filename })}]()\n`; + if (!this._cursorIsOnEmptyLine()) { + placeholder = `\n${placeholder}`; + } + + return placeholder; }, @discourseComputed("composer.requiredCategoryMissing") @@ -888,6 +894,18 @@ export default Component.extend({ return element.tagName === "ASIDE" && element.classList.contains("quote"); }, + _cursorIsOnEmptyLine() { + const textArea = this.element.querySelector(".d-editor-input"); + const selectionStart = textArea.selectionStart; + if (selectionStart === 0) { + return true; + } else if (textArea.value.charAt(selectionStart - 1) === "\n") { + return true; + } else { + return false; + } + }, + actions: { importQuote(toolbarEvent) { this.importQuote(toolbarEvent); diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js index 51e7b1806b8..1413cd5c16d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js @@ -1,4 +1,8 @@ -import { acceptance, queryAll } from "discourse/tests/helpers/qunit-helpers"; +import { + acceptance, + query, + queryAll, +} from "discourse/tests/helpers/qunit-helpers"; import { click, fillIn, visit } from "@ember/test-helpers"; import { test } from "qunit"; @@ -28,7 +32,7 @@ async function writeInComposer(assert) { await fillIn(".d-editor-input", "[test|attachment](upload://asdsad.png)"); } -acceptance("Composer Attachment", function (needs) { +acceptance("Composer Attachment - Cooking", function (needs) { needs.user(); needs.pretender(pretender); @@ -54,3 +58,198 @@ acceptance("Composer Attachment - Secure Media Enabled", function (needs) { ); }); }); + +acceptance("Composer Attachment - Upload Placeholder", function (needs) { + needs.user(); + + test("should insert a newline before and after an image when pasting into an empty composer", async function (assert) { + await visit("/"); + await click("#create-topic"); + const image = createImage("avatar.png", "/images/avatar.png?1", 200, 300); + + await queryAll(".wmd-controls").trigger("fileuploadsend", image); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: avatar.png...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image); + assert.equal( + queryAll(".d-editor-input").val(), + "![avatar|200x300](/images/avatar.png?1)\n" + ); + }); + + test("should insert a newline after an image when pasting into a blank line", async function (assert) { + await visit("/"); + await click("#create-topic"); + await fillIn(".d-editor-input", "The image:\n"); + + const image = createImage("avatar.png", "/images/avatar.png?1", 200, 300); + await queryAll(".wmd-controls").trigger("fileuploadsend", image); + + assert.equal( + queryAll(".d-editor-input").val(), + "The image:\n[Uploading: avatar.png...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image); + assert.equal( + queryAll(".d-editor-input").val(), + "The image:\n![avatar|200x300](/images/avatar.png?1)\n" + ); + }); + + test("should insert a newline before and after an image when pasting into a non blank line", async function (assert) { + await visit("/"); + await click("#create-topic"); + await fillIn(".d-editor-input", "The image:"); + + const image = createImage("avatar.png", "/images/avatar.png?1", 200, 300); + await queryAll(".wmd-controls").trigger("fileuploadsend", image); + + assert.equal( + queryAll(".d-editor-input").val(), + "The image:\n[Uploading: avatar.png...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image); + assert.equal( + queryAll(".d-editor-input").val(), + "The image:\n![avatar|200x300](/images/avatar.png?1)\n" + ); + }); + + test("should insert a newline before and after an image when pasting with cursor in the middle of the line", async function (assert) { + await visit("/"); + await click("#create-topic"); + await fillIn(".d-editor-input", "The image Text after the image."); + const textArea = query(".d-editor-input"); + textArea.selectionStart = 10; + textArea.selectionEnd = 10; + + const image = createImage("avatar.png", "/images/avatar.png?1", 200, 300); + await queryAll(".wmd-controls").trigger("fileuploadsend", image); + + assert.equal( + queryAll(".d-editor-input").val(), + "The image \n[Uploading: avatar.png...]()\nText after the image." + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image); + assert.equal( + queryAll(".d-editor-input").val(), + "The image \n![avatar|200x300](/images/avatar.png?1)\nText after the image." + ); + }); + + test("should insert a newline before and after an image when pasting with text selected", async function (assert) { + await visit("/"); + await click("#create-topic"); + const image = createImage("avatar.png", "/images/avatar.png?1", 200, 300); + await fillIn( + ".d-editor-input", + "The image [paste here] Text after the image." + ); + const textArea = query(".d-editor-input"); + textArea.selectionStart = 10; + textArea.selectionEnd = 23; + + await queryAll(".wmd-controls").trigger("fileuploadsend", image); + assert.equal( + queryAll(".d-editor-input").val(), + "The image \n[Uploading: avatar.png...]()\n Text after the image." + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image); + assert.equal( + queryAll(".d-editor-input").val(), + "The image \n![avatar|200x300](/images/avatar.png?1)\n Text after the image." + ); + }); + + test("pasting several images", async function (assert) { + await visit("/"); + await click("#create-topic"); + + const image1 = createImage("test.png", "/images/avatar.png?1", 200, 300); + const image2 = createImage("test.png", "/images/avatar.png?2", 100, 200); + const image3 = createImage("image.png", "/images/avatar.png?3", 300, 400); + const image4 = createImage("image.png", "/images/avatar.png?4", 300, 400); + + await queryAll(".wmd-controls").trigger("fileuploadsend", image1); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: test.png...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploadsend", image2); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: test.png...]()\n[Uploading: test.png(1)...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploadsend", image4); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: test.png...]()\n[Uploading: test.png(1)...]()\n[Uploading: image.png...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploadsend", image3); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: test.png...]()\n[Uploading: test.png(1)...]()\n[Uploading: image.png...]()\n[Uploading: image.png(1)...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image2); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: test.png...]()\n![test|100x200](/images/avatar.png?2)\n[Uploading: image.png...]()\n[Uploading: image.png(1)...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image3); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: test.png...]()\n![test|100x200](/images/avatar.png?2)\n[Uploading: image.png...]()\n![image|300x400](/images/avatar.png?3)\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image1); + assert.equal( + queryAll(".d-editor-input").val(), + "![test|200x300](/images/avatar.png?1)\n![test|100x200](/images/avatar.png?2)\n[Uploading: image.png...]()\n![image|300x400](/images/avatar.png?3)\n" + ); + }); + + test("should accept files with unescaped characters", async function (assert) { + await visit("/"); + await click("#create-topic"); + + const image = createImage("ima++ge.png", "/images/avatar.png?4", 300, 400); + + await queryAll(".wmd-controls").trigger("fileuploadsend", image); + assert.equal( + queryAll(".d-editor-input").val(), + "[Uploading: ima++ge.png...]()\n" + ); + + await queryAll(".wmd-controls").trigger("fileuploaddone", image); + assert.equal( + queryAll(".d-editor-input").val(), + "![ima++ge|300x400](/images/avatar.png?4)\n" + ); + }); + + function createImage(name, url, width, height) { + const file = new Blob([""], { type: "image/png" }); + file.name = name; + return { + files: [file], + result: { + original_filename: name, + thumbnail_width: width, + thumbnail_height: height, + url: url, + }, + }; + } +}); diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 99236da5eb9..f60b38f3d67 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -118,102 +118,6 @@ acceptance("Composer", function (needs) { assert.ok(!exists(".bootbox.modal"), "the confirmation can be cancelled"); }); - test("Composer upload placeholder", async function (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: "/images/avatar.png?1", - }, - }; - - 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: "/images/avatar.png?2", - }, - }; - - 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: "/images/avatar.png?3", - }, - }; - - const file4 = new Blob([""], { type: "image/png" }); - file4.name = "ima++ge.png"; - const data4 = { - files: [file4], - result: { - original_filename: "ima++ge.png", - thumbnail_width: 300, - thumbnail_height: 400, - url: "/images/avatar.png?3", - }, - }; - - await queryAll(".wmd-controls").trigger("fileuploadsend", data1); - assert.equal( - queryAll(".d-editor-input").val(), - "[Uploading: test.png...]() " - ); - - await queryAll(".wmd-controls").trigger("fileuploadsend", data2); - assert.equal( - queryAll(".d-editor-input").val(), - "[Uploading: test.png...]() [Uploading: test.png(1)...]() " - ); - - await queryAll(".wmd-controls").trigger("fileuploadsend", data4); - assert.equal( - queryAll(".d-editor-input").val(), - "[Uploading: test.png...]() [Uploading: test.png(1)...]() [Uploading: ima++ge.png...]() ", - "should accept files with unescaped characters" - ); - - await queryAll(".wmd-controls").trigger("fileuploadsend", data3); - assert.equal( - queryAll(".d-editor-input").val(), - "[Uploading: test.png...]() [Uploading: test.png(1)...]() [Uploading: ima++ge.png...]() [Uploading: image.png...]() " - ); - - await queryAll(".wmd-controls").trigger("fileuploaddone", data2); - assert.equal( - queryAll(".d-editor-input").val(), - "[Uploading: test.png...]() ![test|100x200](/images/avatar.png?2) [Uploading: ima++ge.png...]() [Uploading: image.png...]() " - ); - - await queryAll(".wmd-controls").trigger("fileuploaddone", data3); - assert.equal( - queryAll(".d-editor-input").val(), - "[Uploading: test.png...]() ![test|100x200](/images/avatar.png?2) [Uploading: ima++ge.png...]() ![image|300x400](/images/avatar.png?3) " - ); - - await queryAll(".wmd-controls").trigger("fileuploaddone", data1); - assert.equal( - queryAll(".d-editor-input").val(), - "![test|200x300](/images/avatar.png?1) ![test|100x200](/images/avatar.png?2) [Uploading: ima++ge.png...]() ![image|300x400](/images/avatar.png?3) " - ); - }); - test("Create a topic with server side errors", async function (assert) { await visit("/"); await click("#create-topic");