[FIX] web_editor: check/uncheck checkboxes in readonly fields

When a checklist is present in an html field, there is a feature that
allows us to check/uncheck its boxes without going through edit mode.
That feature was broken due to a dead callback in wysiwyg.js, which is
hereby restored.
Meanwhile, the behaviour of checklists had changed but the python code
handling the readonly checking/unchecking hadn't so this brings it up to
date[1].
Finally, the code managing checklist ids was unnecessarily complicated
as it was trying to keep the ids consecutive for no particular reason.
This refactors it to simply use unique ids based on date stamps.

[1] It used to be that checking a box checked all the children of that
box but it was in the meantime decided not to do that anymore.

Part-of: odoo/odoo#81920
This commit is contained in:
Antoine Guenet
2022-01-14 10:21:30 +00:00
parent 8266b978e1
commit 41e98ccbe4
5 changed files with 25 additions and 89 deletions
+12 -51
View File
@@ -118,9 +118,19 @@ class Web_Editor(http.Controller):
htmlelem = etree.fromstring("<div>%s</div>" % value, etree.HTMLParser())
checked = bool(checked)
li = htmlelem.find(".//li[@id='checklist-id-" + str(checklistId) + "']")
li = htmlelem.find(".//li[@id='%s']" % checklistId)
if not li or not self._update_checklist_recursive(li, checked, children=True, ancestors=True):
if li is None:
return value
classname = li.get('class', '')
if ('o_checked' in classname) != checked:
if checked:
classname = '%s o_checked' % classname
else:
classname = re.sub(r"\s?o_checked\s?", '', classname)
li.set('class', classname)
else:
return value
value = etree.tostring(htmlelem[0][0], encoding='utf-8', method='html')[5:-6]
@@ -128,55 +138,6 @@ class Web_Editor(http.Controller):
return value
def _update_checklist_recursive (self, li, checked, children=False, ancestors=False):
if 'checklist-id-' not in li.get('id', ''):
return False
classname = li.get('class', '')
if ('o_checked' in classname) == checked:
return False
# check / uncheck
if checked:
classname = '%s o_checked' % classname
else:
classname = re.sub(r"\s?o_checked\s?", '', classname)
li.set('class', classname)
# propagate to children
if children:
node = li.getnext()
ul = None
if node is not None:
if node.tag == 'ul':
ul = node
if node.tag == 'li' and len(node.getchildren()) == 1 and node.getchildren()[0].tag == 'ul':
ul = node.getchildren()[0]
if ul is not None:
for child in ul.getchildren():
if child.tag == 'li':
self._update_checklist_recursive(child, checked, children=True)
# propagate to ancestors
if ancestors:
allSelected = True
ul = li.getparent()
if ul.tag == 'li':
ul = ul.getparent()
for child in ul.getchildren():
if child.tag == 'li' and 'checklist-id' in child.get('id', '') and 'o_checked' not in child.get('class', ''):
allSelected = False
node = ul.getprevious()
if node is None:
node = ul.getparent().getprevious()
if node is not None and node.tag == 'li':
self._update_checklist_recursive(node, allSelected, ancestors=True)
return True
@http.route('/web_editor/video_url/data', type='json', auth='user', website=True)
def video_url_data(self, video_url, autoplay=False, loop=False,
hide_controls=False, hide_fullscreen=False, hide_yt_logo=False,
@@ -445,6 +445,7 @@ export class OdooEditor extends EventTarget {
this.observer = new MutationObserver(records => {
records = this.filterMutationRecords(records);
if (!records.length) return;
this.dispatchEvent(new Event('contentChanged'));
clearTimeout(this.observerTimeout);
if (this._observerTimeoutUnactive.size === 0) {
this.observerTimeout = setTimeout(() => {
@@ -133,6 +133,11 @@ class Sanitize {
}
}
// Ensure unique ids on checklists.
if (node.nodeName == 'LI' && node.parentElement && node.parentElement.classList.contains('o_checklist')) {
node.setAttribute('id', Math.floor(new Date() * Math.random()));
}
// FIXME not parse out of editable zone...
this._parse(node.firstChild);
this._parse(node.nextSibling);
@@ -454,27 +454,6 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
*/
_onChange: function (ev) {
this._doDebouncedAction.apply(this, arguments);
var $lis = this.$content.find('.note-editable ul.o_checklist > li:not(:has(> ul.o_checklist))');
if (!$lis.length) {
return;
}
var max = 0;
var ids = [];
$lis.map(function () {
var checklistId = parseInt(($(this).attr('id') || '0').replace(/^checklist-id-/, ''));
if (ids.indexOf(checklistId) === -1) {
if (checklistId > max) {
max = checklistId;
}
ids.push(checklistId);
} else {
$(this).removeAttr('id');
}
});
$lis.not('[id]').each(function () {
$(this).attr('id', 'checklist-id-' + (++max));
});
},
/**
* Allows Enter keypress in a textarea (source mode)
@@ -503,7 +482,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
ev.stopPropagation();
ev.preventDefault();
var checked = $(ev.target).hasClass('o_checked');
var checklistId = parseInt(($(ev.target).attr('id') || '0').replace(/^checklist-id-/, ''));
var checklistId = parseInt($(ev.target).attr('id') || '0');
this._rpc({
route: '/web_editor/checklist',
@@ -133,6 +133,11 @@ const Wysiwyg = Widget.extend({
plugins: options.editorPlugins,
}, editorCollaborationOptions));
this.odooEditor.addEventListener('contentChanged', function () {
self.$editable.trigger('content_changed');
self.trigger_up('wysiwyg_change');
});
const $wrapwrap = $('#wrapwrap');
if ($wrapwrap.length) {
$wrapwrap[0].addEventListener('scroll', this.odooEditor.multiselectionRefresh, { passive: true });
@@ -1515,22 +1520,7 @@ const Wysiwyg = Widget.extend({
}
},
_editorOptions: function () {
var self = this;
var options = Object.assign({}, this.defaultOptions, this.options);
options.onChange = function (html, $editable) {
$editable.trigger('content_changed');
self.trigger_up('wysiwyg_change');
};
options.onUpload = function (attachments) {
self.trigger_up('wysiwyg_attachment', attachments);
};
options.onFocus = function () {
self.trigger_up('wysiwyg_focus');
};
options.onBlur = function () {
self.trigger_up('wysiwyg_blur');
};
return options;
return Object.assign({}, this.defaultOptions, this.options);
},
_insertSnippetMenu: function () {
return this.snippetsMenu.insertBefore(this.$el);