pastedHTML: ` <span>123<div>456</div></span>`,
},
{
- expectedHTML: `<ul><li role="checkbox" tabindex="-1" aria-checked="true" value="1"><span data-lexical-text="true">done</span></li><li role="checkbox" tabindex="-1" aria-checked="false" value="2"><span data-lexical-text="true">todo</span></li><li value="3"><ul><li role="checkbox" tabindex="-1" aria-checked="true" value="1"><span data-lexical-text="true">done</span></li><li role="checkbox" tabindex="-1" aria-checked="false" value="2"><span data-lexical-text="true">todo</span></li></ul></li><li role="checkbox" tabindex="-1" aria-checked="false" value="3"><span data-lexical-text="true">todo</span></li></ul>`,
+ expectedHTML: `<ul><li role="checkbox" tabindex="-1" aria-checked="true" value="1"><span style="color: rgb(0, 0, 0);" data-lexical-text="true">done</span></li><li role="checkbox" tabindex="-1" aria-checked="false" value="2"><span style="color: rgb(0, 0, 0);" data-lexical-text="true">todo</span></li><li value="3"><ul><li role="checkbox" tabindex="-1" aria-checked="true" value="1"><span style="color: rgb(0, 0, 0);" data-lexical-text="true">done</span></li><li role="checkbox" tabindex="-1" aria-checked="false" value="2"><span style="color: rgb(0, 0, 0);" data-lexical-text="true">todo</span></li></ul></li><li role="checkbox" tabindex="-1" aria-checked="false" value="3"><span style="color: rgb(0, 0, 0);" data-lexical-text="true">todo</span></li></ul>`,
name: 'google doc checklist',
pastedHTML: `<meta charset='utf-8'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-1980f960-7fff-f4df-4ba3-26c6e1508542"><ul style="margin-top:0;margin-bottom:0;padding-inline-start:28px;"><li role="checkbox" aria-checked="true" style="list-style-type:none;font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:line-through;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;" aria-level="1"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAABbElEQVR4Ae3bsU4CYRDEcRsxodZE8Q0BbS258l5MwESJNL6HOfrPKdhyxeBcwk5mkn9F98sGIOSuPM/zPM/zPI+xG/SEtuiAWpEOaIOWaDIWziP6RK14OzSjX44ITvTBvqRn1MRaMIHeBIE2TKBBEGhgArWkKmtJBjKQgQxkIANd/Aw0NVC+O7RHvYFynHasN1COE/UGynGiXgOIjxOtdIH4OGJAfBwxID6OGBAfRwiIjyMARMCpCjRF5+72Dzhd5R+rHfpC92NeTlWgLl5PkQg4RYBynBSJgFMGKMNJkQg4lYFeUDuFRMCpBXQOEgGnDtA/kPg4xT7m2y/tCd9zKgOdviTC5RQEIiAFjh4QASlw9IAISIEjCURAWvmf1UDKcQwUSDmOgWLdMcxA7BnIQAYykIEM5EcRvplAW0GgNRNoKQg0ZwJN0E4I5x1dI+pmgSSA84BG2QQt0LrYG/eAXtGccjme53me53me9wPjPWZWjhktAQAAAABJRU5ErkJggg==" width="18.4px" height="18.4px" alt="checked" aria-roledescription="checkbox" style="margin-right:3px;" /><p style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;display:inline-block;vertical-align:top;margin-top:0;" role="presentation"><span style="font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:line-through;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">done</span></p></li><li role="checkbox" aria-checked="false" style="list-style-type:none;font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAA1ElEQVR4Ae3bMQ4BURSFYY2xBuwQ7BIkTGxFRj9Oo9RdkXn5TvL3L19u+2ZmZmZmZhVbpH26pFcaJ9IrndMudb/CWadHGiden1bll9MIzqd79SUd0thY20qga4NA50qgoUGgoRJo/NL/V/N+QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyFeEZyXQpUGgUyXQrkGgTSVQl/qGcG5pnkq3Sn0jOMv0k3Vpm05pmNjfsGPalFyOmZmZmdkbSS9cKbtzhxMAAAAASUVORK5CYII=" width="18.4px" height="18.4px" alt="unchecked" aria-roledescription="checkbox" style="margin-right:3px;" /><p style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;display:inline-block;vertical-align:top;margin-top:0;" role="presentation"><span style="font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">todo</span></p></li><ul style="margin-top:0;margin-bottom:0;padding-inline-start:28px;"><li role="checkbox" aria-checked="true" style="list-style-type:none;font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:line-through;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;" aria-level="2"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAABbElEQVR4Ae3bsU4CYRDEcRsxodZE8Q0BbS258l5MwESJNL6HOfrPKdhyxeBcwk5mkn9F98sGIOSuPM/zPM/zPI+xG/SEtuiAWpEOaIOWaDIWziP6RK14OzSjX44ITvTBvqRn1MRaMIHeBIE2TKBBEGhgArWkKmtJBjKQgQxkIANd/Aw0NVC+O7RHvYFynHasN1COE/UGynGiXgOIjxOtdIH4OGJAfBwxID6OGBAfRwiIjyMARMCpCjRF5+72Dzhd5R+rHfpC92NeTlWgLl5PkQg4RYBynBSJgFMGKMNJkQg4lYFeUDuFRMCpBXQOEgGnDtA/kPg4xT7m2y/tCd9zKgOdviTC5RQEIiAFjh4QASlw9IAISIEjCURAWvmf1UDKcQwUSDmOgWLdMcxA7BnIQAYykIEM5EcRvplAW0GgNRNoKQg0ZwJN0E4I5x1dI+pmgSSA84BG2QQt0LrYG/eAXtGccjme53me53me9wPjPWZWjhktAQAAAABJRU5ErkJggg==" width="18.4px" height="18.4px" alt="checked" aria-roledescription="checkbox" style="margin-right:3px;" /><p style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;display:inline-block;vertical-align:top;margin-top:0;" role="presentation"><span style="font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:line-through;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">done</span></p></li><li role="checkbox" aria-checked="false" style="list-style-type:none;font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="2"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAA1ElEQVR4Ae3bMQ4BURSFYY2xBuwQ7BIkTGxFRj9Oo9RdkXn5TvL3L19u+2ZmZmZmZhVbpH26pFcaJ9IrndMudb/CWadHGiden1bll9MIzqd79SUd0thY20qga4NA50qgoUGgoRJo/NL/V/N+QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyFeEZyXQpUGgUyXQrkGgTSVQl/qGcG5pnkq3Sn0jOMv0k3Vpm05pmNjfsGPalFyOmZmZmdkbSS9cKbtzhxMAAAAASUVORK5CYII=" width="18.4px" height="18.4px" alt="unchecked" aria-roledescription="checkbox" style="margin-right:3px;" /><p style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;display:inline-block;vertical-align:top;margin-top:0;" role="presentation"><span style="font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">todo</span></p></li></ul><li role="checkbox" aria-checked="false" style="list-style-type:none;font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAA1ElEQVR4Ae3bMQ4BURSFYY2xBuwQ7BIkTGxFRj9Oo9RdkXn5TvL3L19u+2ZmZmZmZhVbpH26pFcaJ9IrndMudb/CWadHGiden1bll9MIzqd79SUd0thY20qga4NA50qgoUGgoRJo/NL/V/N+QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyFeEZyXQpUGgUyXQrkGgTSVQl/qGcG5pnkq3Sn0jOMv0k3Vpm05pmNjfsGPalFyOmZmZmdkbSS9cKbtzhxMAAAAASUVORK5CYII=" width="18.4px" height="18.4px" alt="unchecked" aria-roledescription="checkbox" style="margin-right:3px;" /><p style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;display:inline-block;vertical-align:top;margin-top:0;" role="presentation"><span style="font-size:11.5pt;font-family:'Optimistic Text',sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">todo</span></p></li></ul></b>`,
},
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.
element = wrapElementWith(element, 'b');
}
if (this.hasFormat('italic')) {
- element = wrapElementWith(element, 'i');
+ element = wrapElementWith(element, 'em');
}
if (this.hasFormat('strikethrough')) {
element = wrapElementWith(element, 's');
// 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;
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);
}
$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>',
);
});
import {
$createParagraphNode,
- $createTextNode,
+ $createTextNode, $getEditor,
$getNodeByKey,
$getRoot,
$getSelection,
$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: '',
);
});
+ 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>()!;
cleanup();
expect(html).toBe(
- '<p dir="ltr"><span style="white-space: pre-wrap;">hello world</span></p>',
+ '<p>hello world</p>',
);
});
});
html = $generateHtmlFromNodes(editor, selection);
});
- expect(html).toBe('<span style="white-space: pre-wrap;">World</span>');
+ expect(html).toBe('World');
});
test(`[Lexical -> HTML]: Default selection (undefined) should serialize entire editor state`, () => {
});
expect(html).toBe(
- '<p><span style="white-space: pre-wrap;">Hello</span></p><p><span style="white-space: pre-wrap;">World</span></p>',
+ '<p>Hello</p><p>World</p>',
);
});
});
expect(html).toBe(
- '<p style="text-align: center;"><span style="white-space: pre-wrap;">Hello world!</span></p>',
+ '<p style="text-align: center;">Hello world!</p>',
);
});
});
expect(html).toBe(
- '<p style="text-align: center;"><span style="white-space: pre-wrap;">Hello world!</span></p>',
+ '<p style="text-align: center;">Hello world!</p>',
);
});
});
// Make sure paragraph is inserted inside empty cells
const emptyCell = '<td><p><br></p></td>';
expect(testEnv.innerHTML).toBe(
- `<table><tr><td><p><span data-lexical-text="true">Hello there</span></p></td><td><p><span data-lexical-text="true">General Kenobi!</span></p></td></tr><tr><td><p><span data-lexical-text="true">Lexical is nice</span></p></td>${emptyCell}</tr></table>`,
+ `<table><tr><td><p><span style="color: rgb(0, 0, 0);" data-lexical-text="true">Hello there</span></p></td><td><p><span style="color: rgb(0, 0, 0);" data-lexical-text="true">General Kenobi!</span></p></td></tr><tr><td><p><span style="color: rgb(0, 0, 0);" data-lexical-text="true">Lexical is nice</span></p></td>${emptyCell}</tr></table>`,
);
});
const suite = [
{
expectedHTML:
- '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><span data-lexical-text="true">Get schwifty!</span></p></div>',
+ '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><span style="color: rgb(0, 0, 0);" data-lexical-text="true">Get schwifty!</span></p></div>',
inputs: [
pasteHTML(
`<b style="font-weight:normal;" id="docs-internal-guid-2c706577-7fff-f54a-fe65-12f480020fac"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Get schwifty!</span></b>`,
},
{
expectedHTML:
- '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><strong class="editor-text-bold" data-lexical-text="true">Get schwifty!</strong></p></div>',
+ '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><strong class="editor-text-bold" style="color: rgb(0, 0, 0);" data-lexical-text="true">Get schwifty!</strong></p></div>',
inputs: [
pasteHTML(
`<b style="font-weight:normal;" id="docs-internal-guid-9db03964-7fff-c26c-8b1e-9484fb3b54a4"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Get schwifty!</span></b>`,
},
{
expectedHTML:
- '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><em class="editor-text-italic" data-lexical-text="true">Get schwifty!</em></p></div>',
+ '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><em class="editor-text-italic" style="color: rgb(0, 0, 0);" data-lexical-text="true">Get schwifty!</em></p></div>',
inputs: [
pasteHTML(
`<b style="font-weight:normal;" id="docs-internal-guid-9db03964-7fff-c26c-8b1e-9484fb3b54a4"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:italic;font-variant:normal;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Get schwifty!</span></b>`,
},
{
expectedHTML:
- '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><span class="editor-text-strikethrough" data-lexical-text="true">Get schwifty!</span></p></div>',
+ '<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><span class="editor-text-strikethrough" style="color: rgb(0, 0, 0);" data-lexical-text="true">Get schwifty!</span></p></div>',
inputs: [
pasteHTML(
`<b style="font-weight:normal;" id="docs-internal-guid-9db03964-7fff-c26c-8b1e-9484fb3b54a4"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:line-through;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Get schwifty!</span></b>`,
{
_: 'split paragraph in between two text nodes',
expectedHtml:
- '<p><span style="white-space: pre-wrap;">Hello</span></p><p><span style="white-space: pre-wrap;">world</span></p>',
+ '<p>Hello</p><p>world</p>',
initialHtml: '<p><span>Hello</span><span>world</span></p>',
splitOffset: 1,
splitPath: [0],
{
_: 'split paragraph before the first text node',
expectedHtml:
- '<p><br></p><p><span style="white-space: pre-wrap;">Hello</span><span style="white-space: pre-wrap;">world</span></p>',
+ '<p><br></p><p>Helloworld</p>',
initialHtml: '<p><span>Hello</span><span>world</span></p>',
splitOffset: 0,
splitPath: [0],
{
_: 'split paragraph after the last text node',
expectedHtml:
- '<p><span style="white-space: pre-wrap;">Hello</span><span style="white-space: pre-wrap;">world</span></p><p><br></p>',
+ '<p>Helloworld</p><p><br></p>',
initialHtml: '<p><span>Hello</span><span>world</span></p>',
splitOffset: 2, // Any offset that is higher than children size
splitPath: [0],
{
_: 'split list items between two text nodes',
expectedHtml:
- '<ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul>' +
- '<ul><li><span style="white-space: pre-wrap;">world</span></li></ul>',
+ '<ul><li>Hello</li></ul>' +
+ '<ul><li>world</li></ul>',
initialHtml: '<ul><li><span>Hello</span><span>world</span></li></ul>',
splitOffset: 1, // Any offset that is higher than children size
splitPath: [0, 0],
_: 'split list items before the first text node',
expectedHtml:
'<ul><li></li></ul>' +
- '<ul><li><span style="white-space: pre-wrap;">Hello</span><span style="white-space: pre-wrap;">world</span></li></ul>',
+ '<ul><li>Helloworld</li></ul>',
initialHtml: '<ul><li><span>Hello</span><span>world</span></li></ul>',
splitOffset: 0, // Any offset that is higher than children size
splitPath: [0, 0],
_: 'split nested list items',
expectedHtml:
'<ul>' +
- '<li><span style="white-space: pre-wrap;">Before</span></li>' +
- '<li><ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul></li>' +
+ '<li>Before</li>' +
+ '<li><ul><li>Hello</li></ul></li>' +
'</ul>' +
'<ul>' +
- '<li><ul><li><span style="white-space: pre-wrap;">world</span></li></ul></li>' +
- '<li><span style="white-space: pre-wrap;">After</span></li>' +
+ '<li><ul><li>world</li></ul></li>' +
+ '<li>After</li>' +
'</ul>',
initialHtml:
'<ul>' +
{
_: 'insert into paragraph in between two text nodes',
expectedHtml:
- '<p><span style="white-space: pre-wrap;">Hello</span></p><test-decorator></test-decorator><p><span style="white-space: pre-wrap;">world</span></p>',
+ '<p>Hello</p><test-decorator></test-decorator><p>world</p>',
initialHtml: '<p><span>Helloworld</span></p>',
selectionOffset: 5, // Selection on text node after "Hello" world
selectionPath: [0, 0],
_: 'insert into nested list items',
expectedHtml:
'<ul>' +
- '<li><span style="white-space: pre-wrap;">Before</span></li>' +
- '<li><ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul></li>' +
+ '<li>Before</li>' +
+ '<li><ul><li>Hello</li></ul></li>' +
'</ul>' +
'<test-decorator></test-decorator>' +
'<ul>' +
- '<li><ul><li><span style="white-space: pre-wrap;">world</span></li></ul></li>' +
- '<li><span style="white-space: pre-wrap;">After</span></li>' +
+ '<li><ul><li>world</li></ul></li>' +
+ '<li>After</li>' +
'</ul>',
initialHtml:
'<ul>' +
{
_: 'insert in the end of paragraph',
expectedHtml:
- '<p><span style="white-space: pre-wrap;">Hello world</span></p>' +
+ '<p>Hello world</p>' +
'<test-decorator></test-decorator>' +
'<p><br></p>',
initialHtml: '<p>Hello world</p>',
expectedHtml:
'<p><br></p>' +
'<test-decorator></test-decorator>' +
- '<p><span style="white-space: pre-wrap;">Hello world</span></p>',
+ '<p>Hello world</p>',
initialHtml: '<p>Hello world</p>',
selectionOffset: 0, // Selection on text node after "Hello" world
selectionPath: [0, 0],
expectedHtml:
'<test-decorator></test-decorator>' +
'<test-decorator></test-decorator>' +
- '<p><span style="white-space: pre-wrap;">Before</span></p>' +
- '<p><span style="white-space: pre-wrap;">After</span></p>',
+ '<p>Before</p>' +
+ '<p>After</p>',
initialHtml:
'<test-decorator></test-decorator>' +
'<p><span>Before</span></p>' +
{
_: 'insert with selection on root child',
expectedHtml:
- '<p><span style="white-space: pre-wrap;">Before</span></p>' +
+ '<p>Before</p>' +
'<test-decorator></test-decorator>' +
- '<p><span style="white-space: pre-wrap;">After</span></p>',
+ '<p>After</p>',
initialHtml: '<p>Before</p><p>After</p>',
selectionOffset: 1,
selectionPath: [],
{
_: 'insert with selection on root end',
expectedHtml:
- '<p><span style="white-space: pre-wrap;">Before</span></p>' +
+ '<p>Before</p>' +
'<test-decorator></test-decorator>',
initialHtml: '<p>Before</p>',
selectionOffset: 1,
## Main Todo
- Mac: Shortcut support via command.
-- Update toolbar overflows to match existing editor, incl. direction dynamic controls
## Secondary Todo
- Table caption text support
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
- Deep check of translation coverage
+- About button & view
+- Mobile display and handling
## Bugs
-- Editor theme classes remain on items after export
-- List selection can get lost on nesting/unnesting
-- Content not properly saving on new pages
\ No newline at end of file
+- List selection can get lost on nesting/unnesting
\ No newline at end of file
});
}
+ getDefaultDirection(): 'rtl' | 'ltr' {
+ return this.getContext().options.textDirection === 'rtl' ? 'rtl' : 'ltr';
+ }
+
protected updateContextToolbars(update: EditorUiStateUpdate): void {
for (let i = this.activeContextToolbars.length - 1; i >= 0; i--) {
const toolbar = this.activeContextToolbars[i];
manager.setContext(context);
// Create primary toolbar
- manager.setToolbar(getMainEditorFullToolbar());
+ manager.setToolbar(getMainEditorFullToolbar(context));
// Register modals
for (const key of Object.keys(modals)) {
import {EditorButton} from "./framework/buttons";
-import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiElement} from "./framework/core";
+import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiContext, EditorUiElement} from "./framework/core";
import {EditorFormatMenu} from "./framework/blocks/format-menu";
import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
import {EditorDropdownButton} from "./framework/blocks/dropdown-button";
import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
import {EditorSeparator} from "./framework/blocks/separator";
-export function getMainEditorFullToolbar(): EditorContainerUiElement {
+export function getMainEditorFullToolbar(context: EditorUiContext): EditorContainerUiElement {
+
+ const inRtlMode = context.manager.getDefaultDirection() === 'rtl';
+
return new EditorSimpleClassContainer('editor-toolbar-main', [
// History state
]),
// Alignment
- new EditorOverflowContainer(6, [ // TODO - Dynamic
+ new EditorOverflowContainer(6, [
new EditorButton(alignLeft),
new EditorButton(alignCenter),
new EditorButton(alignRight),
new EditorButton(alignJustify),
- new EditorButton(directionLTR), // TODO - Dynamic
- new EditorButton(directionRTL), // TODO - Dynamic
- ]),
+ inRtlMode ? new EditorButton(directionLTR) : null,
+ inRtlMode ? new EditorButton(directionRTL) : null,
+ ].filter(x => x !== null)),
// Lists
- new EditorOverflowContainer(5, [
+ new EditorOverflowContainer(3, [
new EditorButton(bulletList),
new EditorButton(numberList),
new EditorButton(taskList),
}
.editor-content-area {
min-height: 100%;
+ padding-block: 1rem;
&:focus {
outline: 0;
}
background-color: #FFF;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.15);
z-index: 99;
- min-width: 120px;
display: flex;
flex-direction: row;
}
}
@include larger-than($xxl) {
+ .page-editor-wysiwyg2024 .page-edit-toolbar,
+ .page-editor-wysiwyg2024 .page-editor-page-area,
.page-editor-wysiwyg .page-edit-toolbar,
.page-editor-wysiwyg .page-editor-page-area {
max-width: 1140px;
}
- .page-editor-wysiwyg .floating-toolbox {
+ .page-editor-wysiwyg .floating-toolbox,
+ .page-editor-wysiwyg2024 .floating-toolbox {
position: absolute;
}
}