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
This commit is contained in:
Ghassan Maslamani 2022-08-04 23:33:23 +03:00 committed by GitHub
parent 46f43d4f7c
commit 2d6bd30dd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 16 deletions

View File

@ -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);
},

View File

@ -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"
);
});
});

View File

@ -70,12 +70,13 @@ function buildScaleButton(selectedScale, scale) {
function buildImageShowAltTextControls(altText) {
return `
<span class="alt-text-readonly-container">
<span class="alt-text" aria-label="${I18n.t(
"composer.image_alt_text.aria_label"
)}">${altText}</span>
<span class="alt-text-edit-btn">
<svg aria-hidden="true" class="fa d-icon d-icon-pencil svg-icon svg-string"><use href="#pencil-alt"></use></svg>
</span>
<span class="alt-text-edit-btn">
<svg aria-hidden="true" class="fa d-icon d-icon-pencil svg-icon svg-string"><use href="#pencil-alt"></use></svg>
</span>
<span class="alt-text" aria-label="${I18n.t(
"composer.image_alt_text.aria_label"
)}">${altText}</span>
</span>
`;
}
@ -94,6 +95,17 @@ function buildImageEditAltTextControls(altText) {
`;
}
function buildImageDeleteButton() {
return `
<span class="delete-image-button" aria-label="${I18n.t(
"composer.delete_image_button"
)}">
<svg class="fa d-icon d-icon-trash-alt svg-icon svg-string" xmlns="http://www.w3.org/2000/svg">
<use href="#far-trash-alt"></use>
</svg>
</span>
`;
}
// 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 += `<span class="button-wrapper" data-image-index="${index}">`;
result += `<span class="scale-btn-container">`;
result += SCALES.map((scale) =>
buildScaleButton(selectedScale, scale)
).join("");
result += `</span>`;
result += buildImageShowAltTextControls(
token.attrs[token.attrIndex("alt")][1]
);
@ -126,6 +131,13 @@ function ruleWithImageControls(oldRule) {
token.attrs[token.attrIndex("alt")][1]
);
result += `<span class="scale-btn-container">`;
result += SCALES.map((scale) =>
buildScaleButton(selectedScale, scale)
).join("");
result += `</span>`;
result += buildImageDeleteButton();
result += "</span></span>";
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]",
]);

View File

@ -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;
}

View File

@ -2304,6 +2304,8 @@ en:
image_alt_text:
aria_label: Alt text for image
delete_image_button: Delete Image
notifications:
tooltip:
regular: