[FIX] web_editor: skipped image conversion to attachment in HtmlField
Commit [1] added a check for the closest ".o_dirty" editable element in order to define which editable is used for obtaining the model and id reference from its dataset, and also to avoid multiple attachment creation for the same image. But such check for the closest ".o-dirty" element evaluates to null when the Wysiwyg is used by the HtmlField component, as such class is not added to editable when saving/committing changes. This resulted in skipping the attachment creation for pasted, dropped or modified images. After this commit, attachment creation for modified images is not skipped if an element with the ".o_dirty" class does not exist. task-3495668 [1]: https://github.com/odoo/odoo/commit/e631bff05a3588f3304b75b6a0018b096c13f68d Part-of: odoo/odoo#135806
This commit is contained in:
@@ -1164,7 +1164,8 @@ export class Wysiwyg extends Component {
|
||||
// o_modified_image_to_save by _saveB64Image to request the saving
|
||||
// of the pre-converted webp resizes and all the equivalent jpgs.
|
||||
const b64Proms = [...editableEl.querySelectorAll('.o_b64_image_to_save')].map(async el => {
|
||||
if (el.closest(".o_dirty") !== editableEl) {
|
||||
const dirtyEditable = el.closest(".o_dirty");
|
||||
if (dirtyEditable && dirtyEditable !== editableEl) {
|
||||
// Do nothing as there is an editable element closer to the
|
||||
// image that will perform the `_saveB64Image()` call with
|
||||
// the correct "resModel" and "resId" parameters.
|
||||
@@ -1173,7 +1174,8 @@ export class Wysiwyg extends Component {
|
||||
await this._saveB64Image(el, resModel, resId);
|
||||
});
|
||||
const modifiedProms = [...editableEl.querySelectorAll('.o_modified_image_to_save')].map(async el => {
|
||||
if (el.closest(".o_dirty") !== editableEl) {
|
||||
const dirtyEditable = el.closest(".o_dirty");
|
||||
if (dirtyEditable && dirtyEditable !== editableEl) {
|
||||
// Do nothing as there is an editable element closer to the
|
||||
// image that will perform the `_saveModifiedImage()` call
|
||||
// with the correct "resModel" and "resId" parameters.
|
||||
|
||||
@@ -8,6 +8,7 @@ import { parseHTML } from "@web_editor/js/editor/odoo-editor/src/utils/utils";
|
||||
import { onRendered } from "@odoo/owl";
|
||||
import { wysiwygData } from "@web_editor/../tests/test_utils";
|
||||
import { OdooEditor } from '@web_editor/js/editor/odoo-editor/src/OdooEditor';
|
||||
import { Wysiwyg } from "@web_editor/js/wysiwyg/wysiwyg";
|
||||
|
||||
// Legacy
|
||||
import legacyEnv from '@web/legacy/js/common_env';
|
||||
@@ -415,7 +416,6 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
|
||||
|
||||
// Replace the empty paragraph with a paragrah containing an unsaved
|
||||
// modified image
|
||||
editor.editable.classList.add("o_dirty");
|
||||
const imageContainerElement = parseHTML(imageContainerHTML).firstChild;
|
||||
let paragraph = editor.editable.querySelector(".test_target");
|
||||
editor.editable.replaceChild(imageContainerElement, paragraph);
|
||||
@@ -436,6 +436,119 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => {
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
QUnit.test("Pasted/dropped images are converted to attachments on save", async (assert) => {
|
||||
assert.expect(4);
|
||||
|
||||
// Patch to get a promise to get the htmlField component instance when
|
||||
// the wysiwyg is instancied.
|
||||
const htmlFieldPromise = makeDeferred();
|
||||
patchWithCleanup(HtmlField.prototype, {
|
||||
async startWysiwyg() {
|
||||
await super.startWysiwyg(...arguments);
|
||||
await nextTick();
|
||||
htmlFieldPromise.resolve(this);
|
||||
}
|
||||
});
|
||||
// Add a partner record
|
||||
serverData.models.partner.records.push({
|
||||
id: 1,
|
||||
txt: "<p class='test_target'><br></p>",
|
||||
});
|
||||
|
||||
const mockRPC = async function (route, args) {
|
||||
if (route === '/web_editor/attachment/add_data') {
|
||||
return {
|
||||
image_src: '/test_image_url.png',
|
||||
access_token: '1234',
|
||||
public: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const pasteImage = async (editor) => {
|
||||
// Create image file.
|
||||
const base64ImageData = "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"
|
||||
const binaryImageData = atob(base64ImageData);
|
||||
const uint8Array = new Uint8Array(binaryImageData.length);
|
||||
for (let i = 0; i < binaryImageData.length; i++) {
|
||||
uint8Array[i] = binaryImageData.charCodeAt(i);
|
||||
}
|
||||
const file = new File([uint8Array], "test_image.png", { type: 'image/png' });
|
||||
|
||||
// Create a promise to get the created img elements
|
||||
const pasteImagePromise = makeDeferred();
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations
|
||||
.filter(mutation => mutation.type === 'childList')
|
||||
.forEach(mutation => {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node instanceof HTMLElement) {
|
||||
pasteImagePromise.resolve(node);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
observer.observe(editor.editable, { subtree: true, childList: true });
|
||||
|
||||
// Simulate paste.
|
||||
editor._onPaste({
|
||||
preventDefault() {},
|
||||
clipboardData: {
|
||||
getData() {},
|
||||
items: [{
|
||||
kind: 'file',
|
||||
type: 'image/png',
|
||||
getAsFile: () => file,
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
const img = await pasteImagePromise;
|
||||
observer.disconnect();
|
||||
return img;
|
||||
}
|
||||
|
||||
// Add the ajax service (legacy), because wysiwyg RPCs use it.
|
||||
patchWithCleanup(legacyEnv, {
|
||||
services: {
|
||||
...legacyEnv.services,
|
||||
ajax: {
|
||||
rpc: mockRPC,
|
||||
},
|
||||
}
|
||||
});
|
||||
await makeView({
|
||||
type: "form",
|
||||
resId: 1,
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="txt" widget="html"/>
|
||||
</form>`,
|
||||
mockRPC: mockRPC,
|
||||
});
|
||||
// Let the htmlField be mounted and recover the Component instance.
|
||||
const htmlField = await htmlFieldPromise;
|
||||
const editor = htmlField.wysiwyg.odooEditor;
|
||||
|
||||
const paragraph = editor.editable.querySelector(".test_target");
|
||||
Wysiwyg.setRange(paragraph);
|
||||
|
||||
// Paste image.
|
||||
const img = await pasteImage(editor);
|
||||
// Test environment replaces 'src' by 'data-src'.
|
||||
assert.ok(/^data:image\/png;base64,/.test(img.dataset['src']));
|
||||
assert.ok(img.classList.contains('o_b64_image_to_save'));
|
||||
|
||||
// Save changes.
|
||||
// Restore 'src' attribute so that SavePendingImages can do its job.
|
||||
img.src = img.dataset['src'];
|
||||
await htmlField.commitChanges();
|
||||
assert.equal(img.dataset['src'], '/test_image_url.png?access_token=1234');
|
||||
assert.ok(!img.classList.contains('o_b64_image_to_save'));
|
||||
});
|
||||
|
||||
QUnit.module('Odoo fields synchronisation');
|
||||
|
||||
QUnit.test("Synchronise fields when editing.", async (assert) => {
|
||||
|
||||
Reference in New Issue
Block a user