[IMP] website: simplify the text highlight code
The goal of this commit is to improve the current text highlight code by adding the following updates: - The editor's code (`_handleTextOptions()`) will only set text highlight classes and activate the snippet. Highlight DOM adaptations (multi-line detection, setting SVGs...) are done by the public widget. - The `o_text_highlight_item_dirty` class is no longer used to adapt non-detected highlight items (the highlight mutation observer was tweaked to detect and adapt the new highlight items added to the DOM after editors commands. E.g. after the `oEnter()`). task-3285817 Part-of: odoo/odoo#142968
This commit is contained in:
@@ -19,8 +19,8 @@ import { hasTouch } from "@web/core/browser/feature_detection";
|
||||
import { SIZES, utils as uiUtils } from "@web/core/ui/ui_service";
|
||||
import {
|
||||
applyTextHighlight,
|
||||
removeTextHighlight,
|
||||
switchTextHighlight,
|
||||
getCurrentTextHighlight,
|
||||
} from "@website/js/text_processing";
|
||||
import { touching } from "@web/core/utils/ui";
|
||||
|
||||
@@ -1900,7 +1900,7 @@ registry.TextHighlight = publicWidget.Widget.extend({
|
||||
// information to build the effects, So we need to make the adaptation
|
||||
// here to show the SVGs.
|
||||
for (const textEl of this.el.querySelectorAll(".o_text_highlight")) {
|
||||
applyTextHighlight(textEl, getCurrentTextHighlight(textEl));
|
||||
applyTextHighlight(textEl);
|
||||
}
|
||||
// We need to adapt the text highlights on resize, mainly to take in
|
||||
// consideration the rendered line breaks in text nodes...
|
||||
@@ -1908,7 +1908,7 @@ registry.TextHighlight = publicWidget.Widget.extend({
|
||||
this.options.wysiwyg?.odooEditor.observerUnactive("textHighlightResize");
|
||||
for (const textEl of this.el.querySelectorAll(".o_text_highlight")) {
|
||||
// Remove old effect, normalize content, redraw SVGs...
|
||||
switchTextHighlight(textEl, getCurrentTextHighlight(textEl));
|
||||
switchTextHighlight(textEl);
|
||||
}
|
||||
this.options.wysiwyg?.odooEditor.observerActive("textHighlightResize");
|
||||
});
|
||||
@@ -1927,6 +1927,12 @@ registry.TextHighlight = publicWidget.Widget.extend({
|
||||
if (!this.editableMode) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.observerLocked.clear();
|
||||
} else {
|
||||
// We only save the highlight information on the main text wrapper,
|
||||
// the full structure will be restored on page load.
|
||||
for (const textHighlightEl of this.el.querySelectorAll(".o_text_highlight")) {
|
||||
removeTextHighlight(textHighlightEl);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1981,7 +1987,7 @@ registry.TextHighlight = publicWidget.Widget.extend({
|
||||
this.observerLocked.delete(unit);
|
||||
});
|
||||
// Adapt the highlight, lock new items and observe them.
|
||||
switchTextHighlight(topTextEl, getCurrentTextHighlight(topTextEl));
|
||||
switchTextHighlight(topTextEl);
|
||||
this._lockHighlightObserver(topTextEl);
|
||||
this._observeHighlightResize(topTextEl);
|
||||
});
|
||||
|
||||
@@ -6,19 +6,6 @@ import { UNMERGEABLE_SELECTORS } from "@web_editor/js/editor/odoo-editor/src/uti
|
||||
|
||||
UNMERGEABLE_SELECTORS.push("o_text_highlight_item");
|
||||
|
||||
/**
|
||||
* Used to keep track of duplicated text highlight elements after content
|
||||
* split. The `o_text_highlight_item_dirty` class will be automatically removed
|
||||
* after the highlight adjustment.
|
||||
*/
|
||||
Text.prototype.oEnterOdooEditor = Text.prototype.oEnter;
|
||||
Text.prototype.oEnter = function (offset) {
|
||||
if (this.parentElement?.classList.contains("o_text_highlight_item")) {
|
||||
this.parentElement.classList.add("o_text_highlight_item_dirty");
|
||||
}
|
||||
this.oEnterOdooEditor(...arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to prevent handling the text highlight SVG the same way as text
|
||||
* content on backward deletion.
|
||||
|
||||
@@ -35,23 +35,4 @@ patch(OdooEditor.prototype, {
|
||||
e.clipboardData.setData("text/odoo-editor", html);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
execCommand() {
|
||||
const sel = this.document.getSelection();
|
||||
const range = sel.getRangeAt(0);
|
||||
const startContainer = range.startContainer;
|
||||
const textEl = (startContainer.nodeType === Node.ELEMENT_NODE ?
|
||||
startContainer : startContainer.parentElement).closest(".o_text_highlight");
|
||||
if (textEl) {
|
||||
// In the same way as for `oEnter()`, some other editor commands
|
||||
// may lead to split the content of text highlight lines. We need to
|
||||
// keep track of them to make sure the update is done correctly.
|
||||
[...textEl.querySelectorAll(".o_text_highlight_item")].forEach(line => {
|
||||
line.classList.add("o_text_highlight_item_dirty");
|
||||
});
|
||||
}
|
||||
return super.execCommand(...arguments);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,12 +9,8 @@ import wUtils from "@website/js/utils";
|
||||
import * as OdooEditorLib from "@web_editor/js/editor/odoo-editor/src/utils/utils";
|
||||
import { Component, onMounted, useRef, useState } from "@odoo/owl";
|
||||
import { throttleForAnimation } from "@web/core/utils/timing";
|
||||
import {
|
||||
applyTextHighlight,
|
||||
removeTextHighlight,
|
||||
switchTextHighlight,
|
||||
getCurrentTextHighlight
|
||||
} from "@website/js/text_processing";
|
||||
import { switchTextHighlight } from "@website/js/text_processing";
|
||||
|
||||
const getDeepRange = OdooEditorLib.getDeepRange;
|
||||
const getTraversedNodes = OdooEditorLib.getTraversedNodes;
|
||||
|
||||
@@ -96,27 +92,38 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
// We only update SVGs when the mutation targets text content
|
||||
// (including all mutations leads to infinite loop since the
|
||||
// highlight adjustment will also trigger observed mutations).
|
||||
let target = false;
|
||||
let isSVGMutation = false;
|
||||
let isNewContentMutation = false;
|
||||
const textHighlightEls = new Set();
|
||||
for (const mutation of mutations) {
|
||||
for (const addedNode of mutation.addedNodes) {
|
||||
const addedHighlightNode = addedNode.classlist?.contains("o_text_highlight")
|
||||
? addedNode
|
||||
: addedNode.querySelector?.(":scope .o_text_highlight");
|
||||
if (addedHighlightNode) {
|
||||
// E.g. When applying the split on a node with text
|
||||
// highlights, the `oEnter` command will split the node
|
||||
// and its parents correctly, which leads to duplicated
|
||||
// highlight items that the observer should also handle.
|
||||
// The goal here is to adapt these elements too.
|
||||
textHighlightEls.add(addedHighlightNode);
|
||||
isNewContentMutation = true;
|
||||
}
|
||||
if (addedNode.nodeName === "svg") {
|
||||
isSVGMutation = true;
|
||||
}
|
||||
}
|
||||
// Get the "text highlight" top element affected by mutations.
|
||||
const mutationTarget = (mutation.target.parentElement
|
||||
&& mutation.target.parentElement.closest(".o_text_highlight:not(.o_text_highlight_disabled)"))
|
||||
|| (mutation.target.nodeType === Node.ELEMENT_NODE
|
||||
&& mutation.target.querySelector(":scope .o_text_highlight:not(.o_text_highlight_disabled)"));
|
||||
const mutationTarget = mutation.target.parentElement?.closest(".o_text_highlight")
|
||||
|| mutation.target.nodeType === Node.ELEMENT_NODE
|
||||
&& mutation.target.querySelector(":scope .o_text_highlight");
|
||||
if (mutationTarget) {
|
||||
target = mutationTarget;
|
||||
textHighlightEls.add(mutationTarget);
|
||||
}
|
||||
}
|
||||
if (!isSVGMutation && target) {
|
||||
const highlightID = getCurrentTextHighlight(target);
|
||||
if (highlightID) {
|
||||
this._adaptHighlightOnEdit(target, highlightID);
|
||||
if (!isSVGMutation || isNewContentMutation) {
|
||||
for (const targetEl of textHighlightEls) {
|
||||
this._adaptHighlightOnEdit(targetEl);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -411,6 +418,7 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
* This callback type is used to identify the function used to apply the
|
||||
* text option on a selected text.
|
||||
*
|
||||
* @deprecated
|
||||
* @callback TextOptionCallback
|
||||
* @param {HTMLElement} selectedTextEl The selected text element on which
|
||||
* the option should be applied.
|
||||
@@ -426,6 +434,9 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
* text option's classes, updates...
|
||||
*/
|
||||
_handleTextOptions(targetEl, optionClassList, applyTextOption = () => {}) {
|
||||
// TODO adapt in master
|
||||
const __handleTextOptionsPostActivate = this.__handleTextOptionsPostActivate;
|
||||
|
||||
const classSelector = targetEl.dataset.textSelector;
|
||||
const sel = this._getSelection();
|
||||
if (!this._isValidSelection(sel)) {
|
||||
@@ -476,10 +487,15 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
}
|
||||
if ($snippet) {
|
||||
$snippet[0].normalize();
|
||||
applyTextOption($snippet[0]);
|
||||
applyTextOption($snippet[0]); // TODO remove in master, useless in standard
|
||||
this.trigger_up('activate_snippet', {
|
||||
$snippet: $snippet,
|
||||
previewMode: false,
|
||||
onSuccess: () => {
|
||||
if (__handleTextOptionsPostActivate) {
|
||||
__handleTextOptionsPostActivate($snippet);
|
||||
}
|
||||
},
|
||||
});
|
||||
this.options.wysiwyg.odooEditor.historyStep();
|
||||
} else {
|
||||
@@ -503,25 +519,15 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
/**
|
||||
* Used to adjust the highlight effect when the text content is edited.
|
||||
*
|
||||
* TODO: Should be directly replaced by `switchTextHighlight()` in master
|
||||
* (left in stable for compatibility).
|
||||
*
|
||||
* @private
|
||||
* @param {HTMLElement} target
|
||||
* @param {String} highlightID
|
||||
* @param {String} [highlightID]
|
||||
*/
|
||||
_adaptHighlightOnEdit(target, highlightID) {
|
||||
// Adapt the current highlight to the edited content.
|
||||
switchTextHighlight(target, highlightID);
|
||||
|
||||
// Special use case: When applying the split on a node with text
|
||||
// highlights, the `oEnter` command will split the node and its
|
||||
// parents correctly, which leads to duplicated highlight items
|
||||
// that the `textHighlightObserver` can't handle. The goal of
|
||||
// this code is to force an adaptation on these elements too...
|
||||
[...this.$body[0].ownerDocument.querySelectorAll(".o_text_highlight_item_dirty")].forEach(splitEl => {
|
||||
const newTarget = splitEl.closest(".o_text_highlight");
|
||||
if (newTarget) {
|
||||
this._adaptHighlightOnEdit(newTarget, getCurrentTextHighlight(newTarget));
|
||||
}
|
||||
});
|
||||
return switchTextHighlight(target, highlightID);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -677,18 +683,18 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
*/
|
||||
_onTextHighlightClick(ev) {
|
||||
const target = ev.currentTarget;
|
||||
this._handleTextOptions(target, [
|
||||
this._getOptionTextClass(target),
|
||||
"o_text_highlight_underline",
|
||||
"o_text_highlight_disabled"
|
||||
], selectedTextEl => {
|
||||
const style = window.getComputedStyle(selectedTextEl);
|
||||
// The default value for `--text-highlight-width` is 0.1em.
|
||||
const widthPxValue = `${Math.round(parseFloat(style.fontSize) * 0.1)}px`;
|
||||
selectedTextEl.style.setProperty("--text-highlight-width", widthPxValue);
|
||||
applyTextHighlight(selectedTextEl, "underline");
|
||||
selectedTextEl.classList.remove("o_text_highlight_disabled");
|
||||
});
|
||||
this.__handleTextOptionsPostActivate = ($snippet) => {
|
||||
// TODO should be reviewed
|
||||
$snippet.data("snippet-editor")?.trigger_up("option_update", {
|
||||
optionName: "TextHighlight",
|
||||
name: "new_text_highlight",
|
||||
});
|
||||
};
|
||||
this._handleTextOptions(
|
||||
target,
|
||||
[this._getOptionTextClass(target), "o_text_highlight_underline"],
|
||||
);
|
||||
delete this.__handleTextOptionsPostActivate;
|
||||
},
|
||||
/**
|
||||
* On reload bundles, when it's from the theme tab, destroy any
|
||||
@@ -820,8 +826,8 @@ weSnippetEditor.SnippetEditor.include({
|
||||
* @private
|
||||
*/
|
||||
_adaptOnOptionResize() {
|
||||
[...this.$target[0].querySelectorAll(".o_text_highlight:not(.o_text_highlight_disabled)")].forEach(textEl => {
|
||||
switchTextHighlight(textEl, getCurrentTextHighlight(textEl));
|
||||
[...this.$target[0].querySelectorAll(".o_text_highlight")].forEach(textEl => {
|
||||
switchTextHighlight(textEl);
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -889,11 +895,6 @@ wSnippetMenu.include({
|
||||
}
|
||||
optionsEl.remove();
|
||||
}
|
||||
// We only save the highlight information on the main text wrapper,
|
||||
// the full structure will be restaured on page load.
|
||||
for (const textHighlightEl of getFromEditable(".o_text_highlight")) {
|
||||
removeTextHighlight(textHighlightEl);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
import { renderToElement, renderToFragment } from "@web/core/utils/render";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import {
|
||||
applyTextHighlight,
|
||||
removeTextHighlight,
|
||||
drawTextHighlightSVG,
|
||||
} from "@website/js/text_processing";
|
||||
@@ -3751,6 +3750,16 @@ options.registry.TextHighlight = options.Class.extend({
|
||||
onBlur() {
|
||||
this.leftPanelEl.appendChild(this.el);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
notify(name, data) {
|
||||
// Apply the highlight effect DOM structure when added for the first time.
|
||||
if (name === "new_text_highlight") {
|
||||
this._autoAdaptHighlights();
|
||||
}
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
@@ -3790,9 +3799,20 @@ options.registry.TextHighlight = options.Class.extend({
|
||||
svg.remove();
|
||||
});
|
||||
} else {
|
||||
applyTextHighlight(this.$target[0], highlightID);
|
||||
this._autoAdaptHighlights();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Used to set the highlight effect DOM structure on the targeted text
|
||||
* content.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_autoAdaptHighlights() {
|
||||
this.trigger_up("snippet_edition_request", { exec: async () =>
|
||||
await this._refreshPublicWidgets($(this.options.wysiwyg.odooEditor.editable))
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
|
||||
@@ -289,6 +289,15 @@ export function drawTextHighlightSVG(textEl, highlightID) {
|
||||
* @param {String} highlightID
|
||||
*/
|
||||
export function applyTextHighlight(topTextEl, highlightID) {
|
||||
// Don't reapply the effects to a highlighted text.
|
||||
if (topTextEl.querySelector(".o_text_highlight_item")) {
|
||||
return;
|
||||
}
|
||||
const style = window.getComputedStyle(topTextEl);
|
||||
if (!style.getPropertyValue("--text-highlight-width")) {
|
||||
// The default value for `--text-highlight-width` is 0.1em.
|
||||
topTextEl.style.setProperty("--text-highlight-width", `${Math.round(parseFloat(style.fontSize) * 0.1)}px`);
|
||||
}
|
||||
const lines = [];
|
||||
let lineIndex = 0;
|
||||
const nodeIsBR = node => node.nodeName === "BR";
|
||||
@@ -348,7 +357,7 @@ export function applyTextHighlight(topTextEl, highlightID) {
|
||||
}));
|
||||
// Build and set highlight SVGs.
|
||||
[...topTextEl.querySelectorAll(".o_text_highlight_item")].forEach(container => {
|
||||
container.append(drawTextHighlightSVG(container, highlightID));
|
||||
container.append(drawTextHighlightSVG(container, highlightID || getCurrentTextHighlight(topTextEl)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -389,6 +398,7 @@ export function removeTextHighlight(topTextEl) {
|
||||
* if we just want to adapt the effect).
|
||||
*/
|
||||
export function switchTextHighlight(textEl, highlightID) {
|
||||
highlightID = highlightID || getCurrentTextHighlight(textEl);
|
||||
const ownerDocument = textEl.ownerDocument;
|
||||
const sel = ownerDocument.getSelection();
|
||||
const restoreSelection = sel.rangeCount === 1 && textEl.contains(sel.anchorNode);
|
||||
@@ -428,7 +438,6 @@ export function switchTextHighlight(textEl, highlightID) {
|
||||
...getOffsetNode(textEl, cursorEndPosition)
|
||||
);
|
||||
}
|
||||
ownerDocument.dispatchEvent(new Event("selectionchange"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user