[FIX] web_editor: ensure all formats use spans

The bold format is overridden to use inline style on a <span> rather
than a <b>. The other formats (italic, underline, strikethrough)
continued to use the default browser behavior. This harmonizes them.

X-original-commit: ce59bbd1c13d7fd4cc6971b89cc95386d95fcd75
Part-of: odoo/odoo#86930
This commit is contained in:
Antoine Guenet
2022-03-22 22:12:53 +01:00
parent 7411fc970b
commit 3164697a7e
4 changed files with 80 additions and 48 deletions
@@ -43,6 +43,9 @@ import {
getUrlsInfosInString,
URL_REGEX,
isBold,
isItalic,
isUnderline,
isStrikeThrough,
YOUTUBE_URL_GET_VIDEO_ID,
unwrapContents,
peek,
@@ -1901,9 +1904,6 @@ export class OdooEditor extends EventTarget {
}
const paragraphDropdownButton = this.toolbar.querySelector('#paragraphDropdownButton');
for (const commandState of [
'italic',
'underline',
'strikeThrough',
'justifyLeft',
'justifyRight',
'justifyCenter',
@@ -1926,9 +1926,15 @@ export class OdooEditor extends EventTarget {
const closestStartContainer = closestElement(sel.getRangeAt(0).startContainer, '*');
const selectionStartStyle = getComputedStyle(closestStartContainer);
// queryCommandState('bold') does not take stylesheets into account
const button = this.toolbar.querySelector('#bold');
button.classList.toggle('active', isBold(closestStartContainer));
// queryCommandState does not take stylesheets into account
const boldButton = this.toolbar.querySelector('#bold');
boldButton && boldButton.classList.toggle('active', isBold(closestStartContainer));
const italicButton = this.toolbar.querySelector('#italic');
italicButton && italicButton.classList.toggle('active', isItalic(closestStartContainer));
const underlineButton = this.toolbar.querySelector('#underline');
underlineButton && underlineButton.classList.toggle('active', isUnderline(closestStartContainer));
const strikeThroughButton = this.toolbar.querySelector('#strikeThrough');
strikeThroughButton && strikeThroughButton.classList.toggle('active', isStrikeThrough(closestStartContainer));
const fontSizeValue = this.toolbar.querySelector('#fontSizeCurrentValue');
if (fontSizeValue) {
@@ -21,6 +21,7 @@ import {
isBold,
isItalic,
isUnderline,
isStrikeThrough,
isColorGradient,
isContentTextNode,
isShrunkBlock,
@@ -275,6 +276,45 @@ export function applyInlineStyle(editor, applyStyle) {
}
}
}
const styles = {
bold: {
is: isBold,
name: 'fontWeight',
value: 'bolder',
},
italic: {
is: isItalic,
name: 'fontStyle',
value: 'italic',
},
underline: {
is: isUnderline,
name: 'textDecoration',
value: 'underline',
},
strikeThrough: {
is: isStrikeThrough,
name: 'textDecoration',
value: 'line-through',
}
}
export function toggleFormat(editor, format) {
const selection = editor.document.getSelection();
if (!selection.rangeCount || selection.getRangeAt(0).collapsed) return;
getDeepRange(editor.editable, { splitText: true, select: true, correctTripleClick: true });
const style = styles[format];
const isAlreadyFormatted = getSelectedNodes(editor.editable)
.filter(n => n.nodeType === Node.TEXT_NODE && n.nodeValue.trim().length)
.find(n => style.is(n.parentElement));
applyInlineStyle(editor, el => {
if (isAlreadyFormatted) {
const block = closestBlock(el);
el.style[style.name] = style.is(block) ? 'normal' : getComputedStyle(block)[style.name];
} else {
el.style[style.name] = style.value;
}
});
}
function addColumn(editor, beforeOrAfter) {
getDeepRange(editor.editable, { select: true }); // Ensure deep range for finding td.
const c = getInSelection(editor.document, 'td');
@@ -368,36 +408,10 @@ export const editorCommands = {
// Formats
// -------------------------------------------------------------------------
bold: editor => {
const nodes = getTextNodes(editor);
if (!nodes) return;
const isAlreadyBold = nodes.find(n => isBold(n.parentElement));
applyInlineStyle(editor, el => {
if (isAlreadyBold) {
const block = closestBlock(el);
el.style.fontWeight = isBold(block) ? 'normal' : getComputedStyle(block).fontWeight;
} else {
el.style.fontWeight = 'bolder';
}
});
},
italic: editor => {
const nodes = getTextNodes(editor);
if (!nodes) return;
const isAlreadyItalic = nodes.find(n => isItalic(n.parentElement));
applyInlineStyle(editor, el => {
el.style.fontStyle = isAlreadyItalic ? 'normal' : 'italic';
});
},
underline: editor => {
const nodes = getTextNodes(editor);
if (!nodes) return;
const isAlreadyUnderline = nodes.find(n => isUnderline(n.parentElement));
applyInlineStyle(editor, el => {
el.style.textDecoration = isAlreadyUnderline ? 'none' : 'underline';
});
},
strikeThrough: editor => editor.document.execCommand('strikeThrough'),
bold: editor => toggleFormat(editor, 'bold'),
italic: editor => toggleFormat(editor, 'italic'),
underline: editor => toggleFormat(editor, 'underline'),
strikeThrough: editor => toggleFormat(editor, 'strikeThrough'),
removeFormat: editor => {
editor.document.execCommand('removeFormat');
for (const node of getTraversedNodes(editor.editable)) {
@@ -868,24 +868,31 @@ export function isBold(node) {
return fontWeight > 500 || fontWeight > +getComputedStyle(closestBlock(node)).fontWeight;
}
/**
* Return true if the given node font style equal italic
* Return true if the given node appears italic.
*
* @param {Node} node
* @returns {boolean}
*/
export function isItalic(node) {
const fontStyle = getComputedStyle(closestElement(node)).fontStyle;
return fontStyle === 'italic';
return getComputedStyle(closestElement(node)).fontStyle === 'italic';
}
/**
* Return true if the given node text-decoration style equal underline
* Return true if the given node appears underlined.
*
* @param {Node} node
* @returns {boolean}
*/
export function isUnderline(node) {
const textDecoration = getComputedStyle(closestElement(node)).textDecorationLine;
return textDecoration === 'underline';
return getComputedStyle(closestElement(node)).textDecoration === 'underline';
}
/**
* Return true if the given node appears struck through.
*
* @param {Node} node
* @returns {boolean}
*/
export function isStrikeThrough(node) {
return getComputedStyle(closestElement(node)).textDecoration === 'line-through';
}
export function isUnbreakable(node) {
+11 -6
View File
@@ -181,15 +181,20 @@ tour.register('rte_translator', {
mouseup.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el);
el.dispatchEvent(mouseup);
},
}, {
content: "underline",
trigger: '.oe-toolbar #underline',
// This is disabled for now because it reveals a bug that is fixed in saas-15.1
// and considered a tradeoff in 15.0. The bug concerns the invalidation of
// translations when inserting tags with more than one character. Whereas <u>
// didn't trigger an invalidation, <span style="text-decoration-line: underline;">
// does.
// }, {
// content: "underline",
// trigger: '.oe-toolbar #underline',
}, {
content: "save new change",
trigger: 'button[data-action=save]',
extra_trigger: '#wrap.o_dirty p span[style="text-decoration: underline;"]',
}, {
// See comment above.
// extra_trigger: '#wrap.o_dirty p span[style*="text-decoration-line: underline;"]',
}, {
content: "click language dropdown (4)",
trigger: '.js_language_selector .dropdown-toggle',
extra_trigger: 'body:not(.o_wait_reload):not(:has(.note-editor)) a[data-action="edit"]',