diff --git a/addons/website/static/src/snippets/s_table_of_content/options.js b/addons/website/static/src/snippets/s_table_of_content/options.js index b75a41145e7..9fb04952dde 100644 --- a/addons/website/static/src/snippets/s_table_of_content/options.js +++ b/addons/website/static/src/snippets/s_table_of_content/options.js @@ -19,6 +19,8 @@ options.registry.TableOfContent = options.Class.extend({ const config = {attributes: false, childList: true, subtree: true, characterData: true}; this.observer = new MutationObserver(() => this._generateNav()); this.observer.observe(targetNode, config); + // The mutation observer doesn't observe the attributes change, it would + // be too much. Adding content_changed "listener" instead. this.$target.on('content_changed', () => this._generateNav()); return this._super(...arguments); }, @@ -89,11 +91,20 @@ options.registry.TableOfContent = options.Class.extend({ return; } this.options.wysiwyg && this.options.wysiwyg.odooEditor.unbreakableStepUnactive(); + const navEl = this.$target[0].querySelector('.s_table_of_content_navbar'); const headingsEls = this.$target.find(this.targetedElements).toArray() .filter(el => !el.closest('.o_snippet_desktop_invisible')); const areHeadingsEqual = this.oldHeadingsEls.length === headingsEls.length && this.oldHeadingsEls.every((el, i) => el.isEqualNode(headingsEls[i])); - if (areHeadingsEqual) { + const areVisibilityIdsEqual = headingsEls.every((headingEl) => { + const visibilityId = headingEl.closest('section').getAttribute('data-visibility-id'); + const matchingLinkEl = navEl.querySelector(`a[href="#${headingEl.getAttribute('id')}"]`); + const matchingLinkVisibilityId = matchingLinkEl ? matchingLinkEl.getAttribute('data-visibility-id') : null; + // Check if visibilityId matches matchingLinkVisibilityId or both + // are null/undefined + return visibilityId === matchingLinkVisibilityId; + }); + if (areHeadingsEqual && areVisibilityIdsEqual) { // If the content of the navbar before the change of the DOM is // equal to the content of the navbar after the change of the DOM, // then there is no need to regenerate the navbar. @@ -106,15 +117,15 @@ options.registry.TableOfContent = options.Class.extend({ } // We dispose the scrollSpy because the navbar will be updated. this._disposeScrollSpy(); - const $nav = this.$target.find('.s_table_of_content_navbar'); - $nav.empty(); + navEl.innerHTML = ''; headingsEls.forEach((el) => { const $el = $(el); const id = uniqueId("table_of_content_heading_" + new Date().getTime() + "_"); - $('').attr('href', "#" + id) + const visibilityId = $el.closest('section').attr('data-visibility-id'); + $('').attr({ 'href': "#" + id, 'data-visibility-id': visibilityId }) .addClass('table_of_content_link list-group-item list-group-item-action py-2 border-0 rounded-0') .text($el.text()) - .appendTo($nav); + .appendTo(navEl); $el.attr('id', id); $el[0].dataset.anchor = 'true'; });