[IMP] web_editor: use o_editable and o_not_editable as markers

Before this commit, the o_not_editable class was used as a way to
dynamically set contentEditable on a node. This was little more
than  a convulated way to write `node.contentEditable = false`.
Moreover, the o_editable class was not treated the same way and
did not set contentEditable to true.

This commit turns those two classes into markers for contentEditable
values in html. Once those classes are set, the contentEditable
attribute is updated accordingly. It is not updated dynamically
afterwards. For dynamically setting a node as editable or not, use
the contentEditable attribute directly.

Task-2782426

Part-of: odoo/odoo#120690
This commit is contained in:
David Monjoie
2023-07-18 19:27:53 +02:00
committed by David Beguin
parent 82985296e1
commit 17ee75a84d
3 changed files with 13 additions and 42 deletions
@@ -3051,9 +3051,6 @@ var SnippetsMenu = Widget.extend({
// Register the text nodes that needs to be auto-selected on click
this._registerDefaultTexts();
// Force non editable part to contentEditable=false
$html.find('.o_not_editable').attr('contentEditable', false);
// Add the computed template and make elements draggable
this.$el.html($html);
this.$el.append(this.customizePanel);
@@ -109,7 +109,6 @@ const Wysiwyg = Widget.extend({
this._signalOnline = this._signalOnline.bind(this);
this.tooltipTimeouts = [];
Wysiwyg.activeWysiwygs.add(this);
this._oNotEditableObservers = new Map();
this._joinPeerToPeer = this._joinPeerToPeer.bind(this);
},
/**
@@ -178,7 +177,7 @@ const Wysiwyg = Widget.extend({
document: this.options.document,
autohideToolbar: !!this.options.autohideToolbar,
isRootEditable: this.options.isRootEditable,
onPostSanitize: this._setONotEditable.bind(this),
onPostSanitize: this._onPostSanitize.bind(this),
placeholder: this.options.placeholder,
powerboxFilters: this.options.powerboxFilters || [],
showEmptyElementHint: this.options.showEmptyElementHint,
@@ -409,12 +408,12 @@ const Wysiwyg = Widget.extend({
this.setCSSVariables(this.snippetsMenu ? this.snippetsMenu.el : this.toolbar.el);
this.odooEditor.addEventListener('preObserverActive', () => {
// The setONotEditable will be called right after the
// The onPostSanitize will be called right after the
// editor sanitization (to be right before the historyStep).
// If any `.o_not_editable` is created while the observer is
// unactive, now is the time to call `setONotEditable` before the
// unactive, now is the time to call `onPostSanitize` before the
// editor could register a mutation.
this._setONotEditable(this.odooEditor.editable);
this._onPostSanitize(this.odooEditor.editable);
});
if (this.options.autohideToolbar) {
@@ -716,9 +715,6 @@ const Wysiwyg = Widget.extend({
this.odooEditor.document.removeEventListener("keydown", this._signalOnline, true);
this.odooEditor.document.removeEventListener("keyup", this._signalOnline, true);
this.odooEditor.document.removeEventListener('selectionchange', this._onSelectionChange);
for (const observer of this._oNotEditableObservers.values()) {
observer.disconnect();
}
this.odooEditor.destroy();
}
// If peer to peer is initializing, wait for properly closing it.
@@ -2680,40 +2676,17 @@ const Wysiwyg = Widget.extend({
await this._peerToPeerLoading;
},
/**
* Set contenteditable=false for all `.o_not_editable` found within node if
* node is an element.
*
* For all `.o_not_editable` element found, the attribute contenteditable
* will be removed if the class is removed.
* Set `contenteditable` according to `.o_not_editable` and `.o_editable`.
*
* @param {Node} node
*/
_setONotEditable: function (node) {
const nodes = (node && node.querySelectorAll && node.querySelectorAll('.o_not_editable:not([contenteditable=false])')) || [];
for (const node of nodes) {
node.setAttribute('contenteditable', false);
let observer = this._oNotEditableObservers.get(node);
if (!observer) {
observer = new MutationObserver((records) => {
for (const record of records) {
if (record.type === 'attributes' && record.attributeName === 'class') {
// Remove contenteditable=false on nodes that were
// previsouly o_not_editable but are no longer
// o_not_editable.
if (!node.classList.contains('o_not_editable')) {
this.odooEditor.observerUnactive('_setONotEditable');
node.removeAttribute('contenteditable');
this.odooEditor.observerActive('_setONotEditable');
observer.disconnect();
this._oNotEditableObservers.delete(node);
}
}
}
});
this._oNotEditableObservers.set(node, observer);
observer.observe(node, {
attributes: true,
});
_onPostSanitize: function (node) {
if (node?.querySelectorAll) {
for (const element of node.querySelectorAll('.o_editable, .o_not_editable')) {
const editable = element.classList.contains('o_editable');
if (element.isContentEditable !== editable) {
element.contentEditable = editable;
}
}
}
},
@@ -71,6 +71,7 @@ wTourUtils.registerWebsitePreviewTour('link_tools', {
...wTourUtils.clickOnSave(),
// 3. Edit a link after saving the page.
...wTourUtils.clickOnEditAndWaitEditMode(),
clickOnImgStep,
{
content: "The new link content should be odoo website and url odoo.be",
trigger: 'iframe .s_text_image a[href="http://odoo.be"]:contains("odoo website")',