[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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user