[FIX] website, *: keep table of contents title and menu entries in sync

*: web_editor

The table of contents menu entries are generated automatically which
poses a problem in translation mode. The menu translation entries
are not be editable separately, but the users might be trying to.

This commit shows a notification when the user clicks on the menu
entries while in translation mode, explaining that they are generated
from the title entries.
It was initially intended to use a tooltip - but the amount of code
needed to display a tooltip without marking the DOM as modified is
needlessly complex.

To avoid that styles applied on a plain text during translation were
also appearing in the navigation menu, an attempt at adding a span
around them to make sure that their translation was distinct from the
one inside the main content. But this led to the risk of losing
existing translations.
Because of this, and because that situation seems unlikely, any
remaining style in the navigation menu is instead stripped when the
table of content is started to maintain consistency with what is shown
during translation.

An `o_translation_without_style` class has been introduced to indicate
to the synchronization mechanism that only the text must be replicated
for those elements.
For labels that have a different `data-oe-translation-initial-sha` than
their related header, that value is temporarily kept in another
variable, the value is replaced by the one from the header, which make
the synchronization mechanism properly associate them, then on save
the initial value is restored so that the translation is saved for the
right slot.

task-2752391

closes odoo/odoo#116270

X-original-commit: 5776a358e1b42186d2c26c9bc25010a12811f416
Signed-off-by: Outagant Mehdi (mou) <mou@odoo.com>
This commit is contained in:
Tom De Caluwé
2023-03-23 12:14:54 +01:00
committed by Benoit Socias
parent afcf0fba8d
commit 41e3411776
5 changed files with 98 additions and 0 deletions
@@ -1075,6 +1075,10 @@ const Wysiwyg = Widget.extend({
return;
}
let $node = $(field);
// Do not forward "unstyled" copies to other nodes.
if ($node.hasClass('o_translation_without_style')) {
return;
}
let $nodes = $odooFields.filter(function () {
return this !== field;
});
@@ -1127,6 +1131,16 @@ const Wysiwyg = Widget.extend({
}
const html = $node.html();
for (const node of $nodes) {
if (node.classList.contains('o_translation_without_style')) {
// For generated elements such as the navigation
// labels of website's table of content, only the
// text of the referenced translation must be used.
const text = $node.text();
if (node.innerText !== text) {
node.innerText = text;
}
continue;
}
if (node.innerHTML !== html) {
node.innerHTML = html;
}
+14
View File
@@ -9009,6 +9009,13 @@ msgid ""
"site."
msgstr ""
#. module: website
#. odoo-javascript
#: code:addons/website/static/src/components/translator/translator.js:0
#, python-format
msgid "This translation is not editable."
msgstr ""
#. module: website
#. odoo-javascript
#: code:addons/website/static/src/components/dialog/seo.js:0
@@ -9205,6 +9212,13 @@ msgstr ""
msgid "Translate Attribute"
msgstr ""
#. module: website
#. odoo-javascript
#: code:addons/website/static/src/components/translator/translator.js:0
#, python-format
msgid "Translate header in the text. Menu is generated automatically."
msgstr ""
#. module: website
#. odoo-javascript
#: code:addons/website/static/src/components/translator/translator.xml:0
@@ -205,6 +205,40 @@ export class WebsiteTranslator extends WebsiteEditorComponent {
styleEl.sheet.insertRule(`[data-oe-translation-state].o_dirty {background: ${translatedColor} !important;}`);
styleEl.sheet.insertRule(`[data-oe-translation-state="translated"] {background: ${translatedColor} !important;}`);
styleEl.sheet.insertRule(`[data-oe-translation-state] {background: ${toTranslateColor} !important;}`);
const showNotification = ev => {
let message = this.env._t('This translation is not editable.');
if (ev.target.closest('.s_table_of_content_navbar_wrap')) {
message = this.env._t('Translate header in the text. Menu is generated automatically.');
}
this.env.services.notification.add(message, {
type: 'info',
sticky: false,
});
};
for (const translationEl of $editable) {
if (translationEl.closest('.o_not_editable')) {
translationEl.addEventListener('click', showNotification);
}
if (translationEl.closest('.s_table_of_content_navbar_wrap')) {
// Make sure the same translation ids are used.
const href = translationEl.closest('a').getAttribute('href');
const headerEl = translationEl.closest('.s_table_of_content').querySelector(`${href} [data-oe-translation-initial-sha]`);
if (headerEl) {
if (translationEl.dataset.oeTranslationInitialSha !== headerEl.dataset.oeTranslationInitialSha) {
// Use the same identifier for the generated navigation
// label and its associated header so that the general
// synchronization mechanism kicks in.
// The initial value is kept to be restored before save
// in order to keep the translation of the unstyled
// label distinct from the one of the header.
translationEl.dataset.oeTranslationSaveSha = translationEl.dataset.oeTranslationInitialSha;
translationEl.dataset.oeTranslationInitialSha = headerEl.dataset.oeTranslationInitialSha;
}
translationEl.classList.add('o_translation_without_style');
}
}
}
}
markTranslatableNodes() {
@@ -301,6 +301,26 @@ snippetsEditor.SnippetsMenu.include({
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
async cleanForSave() {
// Clean unstyled translations
return this._super(...arguments).then(() => {
for (const el of this.options.editable[0].querySelectorAll('.o_translation_without_style')) {
el.classList.remove('o_translation_without_style');
if (el.dataset.oeTranslationSaveSha) {
el.dataset.oeTranslationInitialSha = el.dataset.oeTranslationSaveSha;
delete el.dataset.oeTranslationSaveSha;
}
}
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -12,6 +12,7 @@ const TableOfContent = publicWidget.Widget.extend({
* @override
*/
async start() {
this._stripNavbarStyles();
await this._super(...arguments);
this.$scrollingElement = $().getScrollingElement();
this.previousPosition = -1;
@@ -36,6 +37,21 @@ const TableOfContent = publicWidget.Widget.extend({
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_stripNavbarStyles() {
// This is needed for styles added on translations when the master text
// has no style.
for (let el of this.el.querySelectorAll('.s_table_of_content_navbar .table_of_content_link')) {
const translationEl = el.querySelector('span[data-oe-translation-state]');
if (translationEl) {
el = translationEl;
}
const text = el.textContent; // Get text from el.
el.textContent = text; // Replace all of el's content with that text.
}
},
/**
* @private
*/