mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-07-30 04:23:11 +03:00
Lexical: Updated toolbar & text node exporting
- Updated toolbar to match existing editor, including dynamic RTL/LTR controls. - Updated text node handling to not include spans and extra classes when not needed. Added & update tests to cover.
This commit is contained in:
@ -624,7 +624,28 @@ export class TextNode extends LexicalNode {
|
||||
element !== null && isHTMLElement(element),
|
||||
'Expected TextNode createDOM to always return a HTMLElement',
|
||||
);
|
||||
element.style.whiteSpace = 'pre-wrap';
|
||||
|
||||
// Wrap up to retain space if head/tail whitespace exists
|
||||
const text = this.getTextContent();
|
||||
if (/^\s|\s$/.test(text)) {
|
||||
element.style.whiteSpace = 'pre-wrap';
|
||||
}
|
||||
|
||||
// Strip editor theme classes
|
||||
for (const className of Array.from(element.classList.values())) {
|
||||
if (className.startsWith('editor-theme-')) {
|
||||
element.classList.remove(className);
|
||||
}
|
||||
}
|
||||
if (element.classList.length === 0) {
|
||||
element.removeAttribute('class');
|
||||
}
|
||||
|
||||
// Remove placeholder tag if redundant
|
||||
if (element.nodeName === 'SPAN' && !element.getAttribute('style')) {
|
||||
element = document.createTextNode(text);
|
||||
}
|
||||
|
||||
// This is the only way to properly add support for most clients,
|
||||
// even if it's semantically incorrect to have to resort to using
|
||||
// <b>, <u>, <s>, <i> elements.
|
||||
@ -632,7 +653,7 @@ export class TextNode extends LexicalNode {
|
||||
element = wrapElementWith(element, 'b');
|
||||
}
|
||||
if (this.hasFormat('italic')) {
|
||||
element = wrapElementWith(element, 'i');
|
||||
element = wrapElementWith(element, 'em');
|
||||
}
|
||||
if (this.hasFormat('strikethrough')) {
|
||||
element = wrapElementWith(element, 's');
|
||||
@ -1329,6 +1350,10 @@ function applyTextFormatFromStyle(
|
||||
// Google Docs uses span tags + vertical-align to specify subscript and superscript
|
||||
const verticalAlign = style.verticalAlign;
|
||||
|
||||
// Styles to copy to node
|
||||
const color = style.color;
|
||||
const backgroundColor = style.backgroundColor;
|
||||
|
||||
return (lexicalNode: LexicalNode) => {
|
||||
if (!$isTextNode(lexicalNode)) {
|
||||
return lexicalNode;
|
||||
@ -1355,6 +1380,18 @@ function applyTextFormatFromStyle(
|
||||
lexicalNode.toggleFormat('superscript');
|
||||
}
|
||||
|
||||
// Apply styles
|
||||
let style = lexicalNode.getStyle();
|
||||
if (color) {
|
||||
style += `color: ${color};`;
|
||||
}
|
||||
if (backgroundColor && backgroundColor !== 'transparent') {
|
||||
style += `background-color: ${backgroundColor};`;
|
||||
}
|
||||
if (style) {
|
||||
lexicalNode.setStyle(style);
|
||||
}
|
||||
|
||||
if (shouldApply && !lexicalNode.hasFormat(shouldApply)) {
|
||||
lexicalNode.toggleFormat(shouldApply);
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ describe('LexicalTabNode tests', () => {
|
||||
$insertDataTransferForRichText(dataTransfer, selection, editor);
|
||||
});
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
'<p><span data-lexical-text="true">Hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></p><p><span data-lexical-text="true">Hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></p>',
|
||||
'<p><span style="color: rgb(0, 0, 0);" data-lexical-text="true">Hello</span><span data-lexical-text="true">\t</span><span style="color: rgb(0, 0, 0);" data-lexical-text="true">world</span></p><p><span style="color: rgb(0, 0, 0);" data-lexical-text="true">Hello</span><span data-lexical-text="true">\t</span><span style="color: rgb(0, 0, 0);" data-lexical-text="true">world</span></p>',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {
|
||||
$createParagraphNode,
|
||||
$createTextNode,
|
||||
$createTextNode, $getEditor,
|
||||
$getNodeByKey,
|
||||
$getRoot,
|
||||
$getSelection,
|
||||
@ -41,6 +41,9 @@ import {
|
||||
$setCompositionKey,
|
||||
getEditorStateTextContent,
|
||||
} from '../../../LexicalUtils';
|
||||
import {Text} from "@codemirror/state";
|
||||
import {$generateHtmlFromNodes} from "@lexical/html";
|
||||
import {formatBold} from "@lexical/selection/__tests__/utils";
|
||||
|
||||
const editorConfig = Object.freeze({
|
||||
namespace: '',
|
||||
@ -792,6 +795,58 @@ describe('LexicalTextNode tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe('exportDOM()', () => {
|
||||
|
||||
test('simple text exports as a text node', async () => {
|
||||
await update(() => {
|
||||
const paragraph = $getRoot().getFirstChild<ElementNode>()!;
|
||||
const textNode = $createTextNode('hello');
|
||||
paragraph.append(textNode);
|
||||
|
||||
const html = $generateHtmlFromNodes($getEditor(), null);
|
||||
expect(html).toBe('<p>hello</p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('simple text wrapped in span if leading or ending spacing', async () => {
|
||||
|
||||
const textByExpectedHtml = {
|
||||
'hello ': '<p><span style="white-space: pre-wrap;">hello </span></p>',
|
||||
' hello': '<p><span style="white-space: pre-wrap;"> hello</span></p>',
|
||||
' hello ': '<p><span style="white-space: pre-wrap;"> hello </span></p>',
|
||||
}
|
||||
|
||||
await update(() => {
|
||||
const paragraph = $getRoot().getFirstChild<ElementNode>()!;
|
||||
for (const [text, expectedHtml] of Object.entries(textByExpectedHtml)) {
|
||||
paragraph.getChildren().forEach(c => c.remove(true));
|
||||
const textNode = $createTextNode(text);
|
||||
paragraph.append(textNode);
|
||||
|
||||
const html = $generateHtmlFromNodes($getEditor(), null);
|
||||
expect(html).toBe(expectedHtml);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('text with formats exports using format elements instead of classes', async () => {
|
||||
await update(() => {
|
||||
const paragraph = $getRoot().getFirstChild<ElementNode>()!;
|
||||
const textNode = $createTextNode('hello');
|
||||
textNode.toggleFormat('bold');
|
||||
textNode.toggleFormat('subscript');
|
||||
textNode.toggleFormat('italic');
|
||||
textNode.toggleFormat('underline');
|
||||
textNode.toggleFormat('code');
|
||||
paragraph.append(textNode);
|
||||
|
||||
const html = $generateHtmlFromNodes($getEditor(), null);
|
||||
expect(html).toBe('<p><u><em><b><code spellcheck="false"><strong>hello</strong></code></b></em></u></p>');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('mergeWithSibling', async () => {
|
||||
await update(() => {
|
||||
const paragraph = $getRoot().getFirstChild<ElementNode>()!;
|
||||
|
Reference in New Issue
Block a user