From 2d6bd30dd8e53f405b5d95d20320083ee1a61793 Mon Sep 17 00:00:00 2001 From: Ghassan Maslamani Date: Thu, 4 Aug 2022 23:33:23 +0300 Subject: [PATCH] FEATURE: add image delete button in preview. (#17624) This commit adds a delete button to the composer preview next to the image scale buttons. Reference: https://meta.discourse.org/t/image-remover-button-to-composer-preview/233005 --- .../app/components/composer-editor.js | 26 +++++++++++ .../acceptance/composer-image-preview-test.js | 35 +++++++++++++++ .../discourse-markdown/image-controls.js | 44 ++++++++++++------- app/assets/stylesheets/common/d-editor.scss | 13 +++++- config/locales/client.en.yml | 2 + 5 files changed, 104 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js index 9a898f8c1d1..83d14ad6413 100644 --- a/app/assets/javascripts/discourse/app/components/composer-editor.js +++ b/app/assets/javascripts/discourse/app/components/composer-editor.js @@ -594,6 +594,8 @@ export default Component.extend(ComposerUploadUppy, { resetImageControls(buttonWrapper) { const imageResize = buttonWrapper.querySelector(".scale-btn-container"); + const imageDelete = buttonWrapper.querySelector(".delete-image-button"); + const readonlyContainer = buttonWrapper.querySelector( ".alt-text-readonly-container" ); @@ -602,6 +604,8 @@ export default Component.extend(ComposerUploadUppy, { ); imageResize.removeAttribute("hidden"); + imageDelete.removeAttribute("hidden"); + readonlyContainer.removeAttribute("hidden"); buttonWrapper.removeAttribute("editing"); editContainer.setAttribute("hidden", "true"); @@ -647,6 +651,7 @@ export default Component.extend(ComposerUploadUppy, { const buttonWrapper = event.target.closest(".button-wrapper"); const imageResize = buttonWrapper.querySelector(".scale-btn-container"); + const imageDelete = buttonWrapper.querySelector(".delete-image-button"); const readonlyContainer = buttonWrapper.querySelector( ".alt-text-readonly-container" @@ -660,6 +665,7 @@ export default Component.extend(ComposerUploadUppy, { buttonWrapper.setAttribute("editing", "true"); imageResize.setAttribute("hidden", "true"); + imageDelete.setAttribute("hidden", "true"); readonlyContainer.setAttribute("hidden", "true"); editContainerInput.value = altText.textContent; editContainer.removeAttribute("hidden"); @@ -687,10 +693,30 @@ export default Component.extend(ComposerUploadUppy, { this.resetImageControls(buttonWrapper); }, + @bind + _handleImageDeleteButtonClick(event) { + if (!event.target.classList.contains("delete-image-button")) { + return; + } + const index = parseInt( + event.target.closest(".button-wrapper").dataset.imageIndex, + 10 + ); + const matchingPlaceholder = + this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX); + this.appEvents.trigger( + "composer:replace-text", + matchingPlaceholder[index], + "", + { regex: IMAGE_MARKDOWN_REGEX, index } + ); + }, + _registerImageAltTextButtonClick(preview) { preview.addEventListener("click", this._handleAltTextEditButtonClick); preview.addEventListener("click", this._handleAltTextOkButtonClick); preview.addEventListener("click", this._handleAltTextCancelButtonClick); + preview.addEventListener("click", this._handleImageDeleteButtonClick); preview.addEventListener("keypress", this._handleAltTextInputKeypress); }, diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-image-preview-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-image-preview-test.js index 0fd854663ab..4c730d62f5c 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-image-preview-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-image-preview-test.js @@ -340,4 +340,39 @@ acceptance("Composer - Image Preview", function (needs) { "alt text updated" ); }); + + test("Image delete button", async function (assert) { + await visit("/"); + await click("#create-topic"); + + let uploads = [ + "![image_example_0|666x500](upload://q4iRxcuSAzfnbUaCsbjMXcGrpaK.jpeg)", + "![image_example_1|481x480](upload://p1ijebM2iyQcUswBffKwMny3gxu.jpeg)", + ]; + + await fillIn(".d-editor-input", uploads.join("\n")); + + uploads[0] = ""; // delete the first image. + + //click on the remove button of the first image + await click(".button-wrapper[data-image-index='0'] .delete-image-button"); + + assert.strictEqual( + query(".d-editor-input").value, + uploads.join("\n"), + "Image should be removed from the editor" + ); + + assert.equal( + query(".d-editor-input").value.includes("image_example_0"), + false, + "It shouldn't have the first image" + ); + + assert.equal( + query(".d-editor-input").value.includes("image_example_1"), + true, + "It should have the second image" + ); + }); }); diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/image-controls.js b/app/assets/javascripts/pretty-text/engines/discourse-markdown/image-controls.js index 56f51b8d207..41519e09ca2 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/image-controls.js +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/image-controls.js @@ -70,12 +70,13 @@ function buildScaleButton(selectedScale, scale) { function buildImageShowAltTextControls(altText) { return ` - ${altText} - - - + + + + + ${altText} `; } @@ -94,6 +95,17 @@ function buildImageEditAltTextControls(altText) { `; } +function buildImageDeleteButton() { + return ` + + + + + + `; +} // We need this to load after `upload-protocol` which is priority 0 export const priority = 1; @@ -112,13 +124,6 @@ function ruleWithImageControls(oldRule) { result += oldRule(tokens, idx, options, env, slf); result += ``; - - result += ``; - result += SCALES.map((scale) => - buildScaleButton(selectedScale, scale) - ).join(""); - result += ``; - result += buildImageShowAltTextControls( token.attrs[token.attrIndex("alt")][1] ); @@ -126,6 +131,13 @@ function ruleWithImageControls(oldRule) { token.attrs[token.attrIndex("alt")][1] ); + result += ``; + result += SCALES.map((scale) => + buildScaleButton(selectedScale, scale) + ).join(""); + result += ``; + result += buildImageDeleteButton(); + result += ""; return result; @@ -148,16 +160,17 @@ export function setup(helper) { "span.scale-btn[data-scale]", "span.button-wrapper[data-image-index]", "span[aria-label]", - + "span[class=delete-image-button]", "span.alt-text-container", - "span.alt-text-readonly-container", "span.alt-text-readonly-container.alt-text", "span.alt-text-readonly-container.alt-text-edit-btn", "svg[class=fa d-icon d-icon-pencil svg-icon svg-string]", "use[href=#pencil-alt]", + "use[href=#far-trash-alt]", "span.alt-text-edit-container", + "span.delete-image-button", "span[hidden=true]", "input[type=text]", "input[class=alt-text-input]", @@ -166,6 +179,7 @@ export function setup(helper) { "use[href=#check]", "button[class=alt-text-edit-cancel btn-default]", "svg[class=fa d-icon d-icon-times svg-icon svg-string]", + "svg[class=fa d-icon d-icon-trash-alt svg-icon svg-string]", "use[href=#times]", ]); diff --git a/app/assets/stylesheets/common/d-editor.scss b/app/assets/stylesheets/common/d-editor.scss index a45c8a5bc9a..55e8cd2348b 100644 --- a/app/assets/stylesheets/common/d-editor.scss +++ b/app/assets/stylesheets/common/d-editor.scss @@ -200,7 +200,8 @@ .scale-btn-container, .alt-text-readonly-container, - .alt-text-edit-container { + .alt-text-edit-container, + .delete-image-button { background: var(--secondary); display: flex; height: var(--resizer-height); @@ -245,6 +246,7 @@ .alt-text-edit-btn { cursor: pointer; + color: var(--tertiary); svg { padding-right: 0.5em; @@ -282,6 +284,15 @@ } } + .delete-image-button { + cursor: pointer; + color: var(--danger); + + .d-icon-trash-alt { + margin-left: 0.5em; + } + } + svg { pointer-events: none; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a41ac41bb29..f198fa309d2 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2304,6 +2304,8 @@ en: image_alt_text: aria_label: Alt text for image + delete_image_button: Delete Image + notifications: tooltip: regular: