From 41e98ccbe435a1ddfa577d184aad8e748e2e69fe Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Mon, 3 Jan 2022 10:00:45 +0000 Subject: [PATCH] [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 --- addons/web_editor/controllers/main.py | 63 ++++--------------- .../static/lib/odoo-editor/src/OdooEditor.js | 1 + .../lib/odoo-editor/src/utils/sanitize.js | 5 ++ .../static/src/js/backend/field_html.js | 23 +------ .../static/src/js/wysiwyg/wysiwyg.js | 22 ++----- 5 files changed, 25 insertions(+), 89 deletions(-) diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 066860dcc75..4370cd19c0a 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -118,9 +118,19 @@ class Web_Editor(http.Controller): htmlelem = etree.fromstring("
%s
" % 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, diff --git a/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js index dc27e0182f5..5e29b49e064 100644 --- a/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js @@ -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(() => { diff --git a/addons/web_editor/static/lib/odoo-editor/src/utils/sanitize.js b/addons/web_editor/static/lib/odoo-editor/src/utils/sanitize.js index f93f8f72769..a62e79fbe2c 100644 --- a/addons/web_editor/static/lib/odoo-editor/src/utils/sanitize.js +++ b/addons/web_editor/static/lib/odoo-editor/src/utils/sanitize.js @@ -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); diff --git a/addons/web_editor/static/src/js/backend/field_html.js b/addons/web_editor/static/src/js/backend/field_html.js index dcb8477e3f0..fc0a0fc288d 100644 --- a/addons/web_editor/static/src/js/backend/field_html.js +++ b/addons/web_editor/static/src/js/backend/field_html.js @@ -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', diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index fa4c8221810..5ce049f3a6c 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -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);