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:
parent
46f43d4f7c
commit
2d6bd30dd8
|
@ -594,6 +594,8 @@ export default Component.extend(ComposerUploadUppy, {
|
||||||
|
|
||||||
resetImageControls(buttonWrapper) {
|
resetImageControls(buttonWrapper) {
|
||||||
const imageResize = buttonWrapper.querySelector(".scale-btn-container");
|
const imageResize = buttonWrapper.querySelector(".scale-btn-container");
|
||||||
|
const imageDelete = buttonWrapper.querySelector(".delete-image-button");
|
||||||
|
|
||||||
const readonlyContainer = buttonWrapper.querySelector(
|
const readonlyContainer = buttonWrapper.querySelector(
|
||||||
".alt-text-readonly-container"
|
".alt-text-readonly-container"
|
||||||
);
|
);
|
||||||
|
@ -602,6 +604,8 @@ export default Component.extend(ComposerUploadUppy, {
|
||||||
);
|
);
|
||||||
|
|
||||||
imageResize.removeAttribute("hidden");
|
imageResize.removeAttribute("hidden");
|
||||||
|
imageDelete.removeAttribute("hidden");
|
||||||
|
|
||||||
readonlyContainer.removeAttribute("hidden");
|
readonlyContainer.removeAttribute("hidden");
|
||||||
buttonWrapper.removeAttribute("editing");
|
buttonWrapper.removeAttribute("editing");
|
||||||
editContainer.setAttribute("hidden", "true");
|
editContainer.setAttribute("hidden", "true");
|
||||||
|
@ -647,6 +651,7 @@ export default Component.extend(ComposerUploadUppy, {
|
||||||
|
|
||||||
const buttonWrapper = event.target.closest(".button-wrapper");
|
const buttonWrapper = event.target.closest(".button-wrapper");
|
||||||
const imageResize = buttonWrapper.querySelector(".scale-btn-container");
|
const imageResize = buttonWrapper.querySelector(".scale-btn-container");
|
||||||
|
const imageDelete = buttonWrapper.querySelector(".delete-image-button");
|
||||||
|
|
||||||
const readonlyContainer = buttonWrapper.querySelector(
|
const readonlyContainer = buttonWrapper.querySelector(
|
||||||
".alt-text-readonly-container"
|
".alt-text-readonly-container"
|
||||||
|
@ -660,6 +665,7 @@ export default Component.extend(ComposerUploadUppy, {
|
||||||
|
|
||||||
buttonWrapper.setAttribute("editing", "true");
|
buttonWrapper.setAttribute("editing", "true");
|
||||||
imageResize.setAttribute("hidden", "true");
|
imageResize.setAttribute("hidden", "true");
|
||||||
|
imageDelete.setAttribute("hidden", "true");
|
||||||
readonlyContainer.setAttribute("hidden", "true");
|
readonlyContainer.setAttribute("hidden", "true");
|
||||||
editContainerInput.value = altText.textContent;
|
editContainerInput.value = altText.textContent;
|
||||||
editContainer.removeAttribute("hidden");
|
editContainer.removeAttribute("hidden");
|
||||||
|
@ -687,10 +693,30 @@ export default Component.extend(ComposerUploadUppy, {
|
||||||
this.resetImageControls(buttonWrapper);
|
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) {
|
_registerImageAltTextButtonClick(preview) {
|
||||||
preview.addEventListener("click", this._handleAltTextEditButtonClick);
|
preview.addEventListener("click", this._handleAltTextEditButtonClick);
|
||||||
preview.addEventListener("click", this._handleAltTextOkButtonClick);
|
preview.addEventListener("click", this._handleAltTextOkButtonClick);
|
||||||
preview.addEventListener("click", this._handleAltTextCancelButtonClick);
|
preview.addEventListener("click", this._handleAltTextCancelButtonClick);
|
||||||
|
preview.addEventListener("click", this._handleImageDeleteButtonClick);
|
||||||
preview.addEventListener("keypress", this._handleAltTextInputKeypress);
|
preview.addEventListener("keypress", this._handleAltTextInputKeypress);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -340,4 +340,39 @@ acceptance("Composer - Image Preview", function (needs) {
|
||||||
"alt text updated"
|
"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"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -70,12 +70,13 @@ function buildScaleButton(selectedScale, scale) {
|
||||||
function buildImageShowAltTextControls(altText) {
|
function buildImageShowAltTextControls(altText) {
|
||||||
return `
|
return `
|
||||||
<span class="alt-text-readonly-container">
|
<span class="alt-text-readonly-container">
|
||||||
|
<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(
|
<span class="alt-text" aria-label="${I18n.t(
|
||||||
"composer.image_alt_text.aria_label"
|
"composer.image_alt_text.aria_label"
|
||||||
)}">${altText}</span>
|
)}">${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>
|
</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
|
// We need this to load after `upload-protocol` which is priority 0
|
||||||
export const priority = 1;
|
export const priority = 1;
|
||||||
|
|
||||||
|
@ -112,13 +124,6 @@ function ruleWithImageControls(oldRule) {
|
||||||
result += oldRule(tokens, idx, options, env, slf);
|
result += oldRule(tokens, idx, options, env, slf);
|
||||||
|
|
||||||
result += `<span class="button-wrapper" data-image-index="${index}">`;
|
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(
|
result += buildImageShowAltTextControls(
|
||||||
token.attrs[token.attrIndex("alt")][1]
|
token.attrs[token.attrIndex("alt")][1]
|
||||||
);
|
);
|
||||||
|
@ -126,6 +131,13 @@ function ruleWithImageControls(oldRule) {
|
||||||
token.attrs[token.attrIndex("alt")][1]
|
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>";
|
result += "</span></span>";
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -148,16 +160,17 @@ export function setup(helper) {
|
||||||
"span.scale-btn[data-scale]",
|
"span.scale-btn[data-scale]",
|
||||||
"span.button-wrapper[data-image-index]",
|
"span.button-wrapper[data-image-index]",
|
||||||
"span[aria-label]",
|
"span[aria-label]",
|
||||||
|
"span[class=delete-image-button]",
|
||||||
"span.alt-text-container",
|
"span.alt-text-container",
|
||||||
|
|
||||||
"span.alt-text-readonly-container",
|
"span.alt-text-readonly-container",
|
||||||
"span.alt-text-readonly-container.alt-text",
|
"span.alt-text-readonly-container.alt-text",
|
||||||
"span.alt-text-readonly-container.alt-text-edit-btn",
|
"span.alt-text-readonly-container.alt-text-edit-btn",
|
||||||
"svg[class=fa d-icon d-icon-pencil svg-icon svg-string]",
|
"svg[class=fa d-icon d-icon-pencil svg-icon svg-string]",
|
||||||
"use[href=#pencil-alt]",
|
"use[href=#pencil-alt]",
|
||||||
|
"use[href=#far-trash-alt]",
|
||||||
|
|
||||||
"span.alt-text-edit-container",
|
"span.alt-text-edit-container",
|
||||||
|
"span.delete-image-button",
|
||||||
"span[hidden=true]",
|
"span[hidden=true]",
|
||||||
"input[type=text]",
|
"input[type=text]",
|
||||||
"input[class=alt-text-input]",
|
"input[class=alt-text-input]",
|
||||||
|
@ -166,6 +179,7 @@ export function setup(helper) {
|
||||||
"use[href=#check]",
|
"use[href=#check]",
|
||||||
"button[class=alt-text-edit-cancel btn-default]",
|
"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-times svg-icon svg-string]",
|
||||||
|
"svg[class=fa d-icon d-icon-trash-alt svg-icon svg-string]",
|
||||||
"use[href=#times]",
|
"use[href=#times]",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -200,7 +200,8 @@
|
||||||
|
|
||||||
.scale-btn-container,
|
.scale-btn-container,
|
||||||
.alt-text-readonly-container,
|
.alt-text-readonly-container,
|
||||||
.alt-text-edit-container {
|
.alt-text-edit-container,
|
||||||
|
.delete-image-button {
|
||||||
background: var(--secondary);
|
background: var(--secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
height: var(--resizer-height);
|
height: var(--resizer-height);
|
||||||
|
@ -245,6 +246,7 @@
|
||||||
|
|
||||||
.alt-text-edit-btn {
|
.alt-text-edit-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: var(--tertiary);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
padding-right: 0.5em;
|
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 {
|
svg {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2304,6 +2304,8 @@ en:
|
||||||
image_alt_text:
|
image_alt_text:
|
||||||
aria_label: Alt text for image
|
aria_label: Alt text for image
|
||||||
|
|
||||||
|
delete_image_button: Delete Image
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tooltip:
|
tooltip:
|
||||||
regular:
|
regular:
|
||||||
|
|
Loading…
Reference in New Issue