mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04: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:
parent
8b32e6c15a
commit
a62d8381be
File diff suppressed because one or more lines are too long
@ -624,7 +624,28 @@ export class TextNode extends LexicalNode {
|
|||||||
element !== null && isHTMLElement(element),
|
element !== null && isHTMLElement(element),
|
||||||
'Expected TextNode createDOM to always return a HTMLElement',
|
'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,
|
// This is the only way to properly add support for most clients,
|
||||||
// even if it's semantically incorrect to have to resort to using
|
// even if it's semantically incorrect to have to resort to using
|
||||||
// <b>, <u>, <s>, <i> elements.
|
// <b>, <u>, <s>, <i> elements.
|
||||||
@ -632,7 +653,7 @@ export class TextNode extends LexicalNode {
|
|||||||
element = wrapElementWith(element, 'b');
|
element = wrapElementWith(element, 'b');
|
||||||
}
|
}
|
||||||
if (this.hasFormat('italic')) {
|
if (this.hasFormat('italic')) {
|
||||||
element = wrapElementWith(element, 'i');
|
element = wrapElementWith(element, 'em');
|
||||||
}
|
}
|
||||||
if (this.hasFormat('strikethrough')) {
|
if (this.hasFormat('strikethrough')) {
|
||||||
element = wrapElementWith(element, 's');
|
element = wrapElementWith(element, 's');
|
||||||
@ -1329,6 +1350,10 @@ function applyTextFormatFromStyle(
|
|||||||
// Google Docs uses span tags + vertical-align to specify subscript and superscript
|
// Google Docs uses span tags + vertical-align to specify subscript and superscript
|
||||||
const verticalAlign = style.verticalAlign;
|
const verticalAlign = style.verticalAlign;
|
||||||
|
|
||||||
|
// Styles to copy to node
|
||||||
|
const color = style.color;
|
||||||
|
const backgroundColor = style.backgroundColor;
|
||||||
|
|
||||||
return (lexicalNode: LexicalNode) => {
|
return (lexicalNode: LexicalNode) => {
|
||||||
if (!$isTextNode(lexicalNode)) {
|
if (!$isTextNode(lexicalNode)) {
|
||||||
return lexicalNode;
|
return lexicalNode;
|
||||||
@ -1355,6 +1380,18 @@ function applyTextFormatFromStyle(
|
|||||||
lexicalNode.toggleFormat('superscript');
|
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)) {
|
if (shouldApply && !lexicalNode.hasFormat(shouldApply)) {
|
||||||
lexicalNode.toggleFormat(shouldApply);
|
lexicalNode.toggleFormat(shouldApply);
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ describe('LexicalTabNode tests', () => {
|
|||||||
$insertDataTransferForRichText(dataTransfer, selection, editor);
|
$insertDataTransferForRichText(dataTransfer, selection, editor);
|
||||||
});
|
});
|
||||||
expect(testEnv.innerHTML).toBe(
|
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 {
|
import {
|
||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
$createTextNode,
|
$createTextNode, $getEditor,
|
||||||
$getNodeByKey,
|
$getNodeByKey,
|
||||||
$getRoot,
|
$getRoot,
|
||||||
$getSelection,
|
$getSelection,
|
||||||
@ -41,6 +41,9 @@ import {
|
|||||||
$setCompositionKey,
|
$setCompositionKey,
|
||||||
getEditorStateTextContent,
|
getEditorStateTextContent,
|
||||||
} from '../../../LexicalUtils';
|
} from '../../../LexicalUtils';
|
||||||
|
import {Text} from "@codemirror/state";
|
||||||
|
import {$generateHtmlFromNodes} from "@lexical/html";
|
||||||
|
import {formatBold} from "@lexical/selection/__tests__/utils";
|
||||||
|
|
||||||
const editorConfig = Object.freeze({
|
const editorConfig = Object.freeze({
|
||||||
namespace: '',
|
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 () => {
|
test('mergeWithSibling', async () => {
|
||||||
await update(() => {
|
await update(() => {
|
||||||
const paragraph = $getRoot().getFirstChild<ElementNode>()!;
|
const paragraph = $getRoot().getFirstChild<ElementNode>()!;
|
||||||
|
@ -206,7 +206,7 @@ describe('LexicalHeadlessEditor', () => {
|
|||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
expect(html).toBe(
|
expect(html).toBe(
|
||||||
'<p dir="ltr"><span style="white-space: pre-wrap;">hello world</span></p>',
|
'<p>hello world</p>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -102,7 +102,7 @@ describe('HTML', () => {
|
|||||||
html = $generateHtmlFromNodes(editor, selection);
|
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`, () => {
|
test(`[Lexical -> HTML]: Default selection (undefined) should serialize entire editor state`, () => {
|
||||||
@ -145,7 +145,7 @@ describe('HTML', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(html).toBe(
|
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>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ describe('HTML', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(html).toBe(
|
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>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ describe('HTML', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(html).toBe(
|
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>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -115,7 +115,7 @@ describe('LexicalTableNode tests', () => {
|
|||||||
// Make sure paragraph is inserted inside empty cells
|
// Make sure paragraph is inserted inside empty cells
|
||||||
const emptyCell = '<td><p><br></p></td>';
|
const emptyCell = '<td><p><br></p></td>';
|
||||||
expect(testEnv.innerHTML).toBe(
|
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>`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ describe('LexicalEventHelpers', () => {
|
|||||||
const suite = [
|
const suite = [
|
||||||
{
|
{
|
||||||
expectedHTML:
|
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: [
|
inputs: [
|
||||||
pasteHTML(
|
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>`,
|
`<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>`,
|
||||||
@ -340,7 +340,7 @@ describe('LexicalEventHelpers', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedHTML:
|
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: [
|
inputs: [
|
||||||
pasteHTML(
|
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>`,
|
`<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>`,
|
||||||
@ -350,7 +350,7 @@ describe('LexicalEventHelpers', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedHTML:
|
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: [
|
inputs: [
|
||||||
pasteHTML(
|
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>`,
|
`<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>`,
|
||||||
@ -360,7 +360,7 @@ describe('LexicalEventHelpers', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedHTML:
|
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: [
|
inputs: [
|
||||||
pasteHTML(
|
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>`,
|
`<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>`,
|
||||||
|
@ -38,7 +38,7 @@ describe('LexicalUtils#splitNode', () => {
|
|||||||
{
|
{
|
||||||
_: 'split paragraph in between two text nodes',
|
_: 'split paragraph in between two text nodes',
|
||||||
expectedHtml:
|
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>',
|
initialHtml: '<p><span>Hello</span><span>world</span></p>',
|
||||||
splitOffset: 1,
|
splitOffset: 1,
|
||||||
splitPath: [0],
|
splitPath: [0],
|
||||||
@ -46,7 +46,7 @@ describe('LexicalUtils#splitNode', () => {
|
|||||||
{
|
{
|
||||||
_: 'split paragraph before the first text node',
|
_: 'split paragraph before the first text node',
|
||||||
expectedHtml:
|
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>',
|
initialHtml: '<p><span>Hello</span><span>world</span></p>',
|
||||||
splitOffset: 0,
|
splitOffset: 0,
|
||||||
splitPath: [0],
|
splitPath: [0],
|
||||||
@ -54,7 +54,7 @@ describe('LexicalUtils#splitNode', () => {
|
|||||||
{
|
{
|
||||||
_: 'split paragraph after the last text node',
|
_: 'split paragraph after the last text node',
|
||||||
expectedHtml:
|
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>',
|
initialHtml: '<p><span>Hello</span><span>world</span></p>',
|
||||||
splitOffset: 2, // Any offset that is higher than children size
|
splitOffset: 2, // Any offset that is higher than children size
|
||||||
splitPath: [0],
|
splitPath: [0],
|
||||||
@ -62,8 +62,8 @@ describe('LexicalUtils#splitNode', () => {
|
|||||||
{
|
{
|
||||||
_: 'split list items between two text nodes',
|
_: 'split list items between two text nodes',
|
||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul>' +
|
'<ul><li>Hello</li></ul>' +
|
||||||
'<ul><li><span style="white-space: pre-wrap;">world</span></li></ul>',
|
'<ul><li>world</li></ul>',
|
||||||
initialHtml: '<ul><li><span>Hello</span><span>world</span></li></ul>',
|
initialHtml: '<ul><li><span>Hello</span><span>world</span></li></ul>',
|
||||||
splitOffset: 1, // Any offset that is higher than children size
|
splitOffset: 1, // Any offset that is higher than children size
|
||||||
splitPath: [0, 0],
|
splitPath: [0, 0],
|
||||||
@ -72,7 +72,7 @@ describe('LexicalUtils#splitNode', () => {
|
|||||||
_: 'split list items before the first text node',
|
_: 'split list items before the first text node',
|
||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<ul><li></li></ul>' +
|
'<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>',
|
initialHtml: '<ul><li><span>Hello</span><span>world</span></li></ul>',
|
||||||
splitOffset: 0, // Any offset that is higher than children size
|
splitOffset: 0, // Any offset that is higher than children size
|
||||||
splitPath: [0, 0],
|
splitPath: [0, 0],
|
||||||
@ -81,12 +81,12 @@ describe('LexicalUtils#splitNode', () => {
|
|||||||
_: 'split nested list items',
|
_: 'split nested list items',
|
||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
'<li><span style="white-space: pre-wrap;">Before</span></li>' +
|
'<li>Before</li>' +
|
||||||
'<li><ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul></li>' +
|
'<li><ul><li>Hello</li></ul></li>' +
|
||||||
'</ul>' +
|
'</ul>' +
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
'<li><ul><li><span style="white-space: pre-wrap;">world</span></li></ul></li>' +
|
'<li><ul><li>world</li></ul></li>' +
|
||||||
'<li><span style="white-space: pre-wrap;">After</span></li>' +
|
'<li>After</li>' +
|
||||||
'</ul>',
|
'</ul>',
|
||||||
initialHtml:
|
initialHtml:
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
|
@ -46,7 +46,7 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
{
|
{
|
||||||
_: 'insert into paragraph in between two text nodes',
|
_: 'insert into paragraph in between two text nodes',
|
||||||
expectedHtml:
|
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>',
|
initialHtml: '<p><span>Helloworld</span></p>',
|
||||||
selectionOffset: 5, // Selection on text node after "Hello" world
|
selectionOffset: 5, // Selection on text node after "Hello" world
|
||||||
selectionPath: [0, 0],
|
selectionPath: [0, 0],
|
||||||
@ -55,13 +55,13 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
_: 'insert into nested list items',
|
_: 'insert into nested list items',
|
||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
'<li><span style="white-space: pre-wrap;">Before</span></li>' +
|
'<li>Before</li>' +
|
||||||
'<li><ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul></li>' +
|
'<li><ul><li>Hello</li></ul></li>' +
|
||||||
'</ul>' +
|
'</ul>' +
|
||||||
'<test-decorator></test-decorator>' +
|
'<test-decorator></test-decorator>' +
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
'<li><ul><li><span style="white-space: pre-wrap;">world</span></li></ul></li>' +
|
'<li><ul><li>world</li></ul></li>' +
|
||||||
'<li><span style="white-space: pre-wrap;">After</span></li>' +
|
'<li>After</li>' +
|
||||||
'</ul>',
|
'</ul>',
|
||||||
initialHtml:
|
initialHtml:
|
||||||
'<ul>' +
|
'<ul>' +
|
||||||
@ -82,7 +82,7 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
{
|
{
|
||||||
_: 'insert in the end of paragraph',
|
_: 'insert in the end of paragraph',
|
||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<p><span style="white-space: pre-wrap;">Hello world</span></p>' +
|
'<p>Hello world</p>' +
|
||||||
'<test-decorator></test-decorator>' +
|
'<test-decorator></test-decorator>' +
|
||||||
'<p><br></p>',
|
'<p><br></p>',
|
||||||
initialHtml: '<p>Hello world</p>',
|
initialHtml: '<p>Hello world</p>',
|
||||||
@ -94,7 +94,7 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<p><br></p>' +
|
'<p><br></p>' +
|
||||||
'<test-decorator></test-decorator>' +
|
'<test-decorator></test-decorator>' +
|
||||||
'<p><span style="white-space: pre-wrap;">Hello world</span></p>',
|
'<p>Hello world</p>',
|
||||||
initialHtml: '<p>Hello world</p>',
|
initialHtml: '<p>Hello world</p>',
|
||||||
selectionOffset: 0, // Selection on text node after "Hello" world
|
selectionOffset: 0, // Selection on text node after "Hello" world
|
||||||
selectionPath: [0, 0],
|
selectionPath: [0, 0],
|
||||||
@ -104,8 +104,8 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<test-decorator></test-decorator>' +
|
'<test-decorator></test-decorator>' +
|
||||||
'<test-decorator></test-decorator>' +
|
'<test-decorator></test-decorator>' +
|
||||||
'<p><span style="white-space: pre-wrap;">Before</span></p>' +
|
'<p>Before</p>' +
|
||||||
'<p><span style="white-space: pre-wrap;">After</span></p>',
|
'<p>After</p>',
|
||||||
initialHtml:
|
initialHtml:
|
||||||
'<test-decorator></test-decorator>' +
|
'<test-decorator></test-decorator>' +
|
||||||
'<p><span>Before</span></p>' +
|
'<p><span>Before</span></p>' +
|
||||||
@ -116,9 +116,9 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
{
|
{
|
||||||
_: 'insert with selection on root child',
|
_: 'insert with selection on root child',
|
||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<p><span style="white-space: pre-wrap;">Before</span></p>' +
|
'<p>Before</p>' +
|
||||||
'<test-decorator></test-decorator>' +
|
'<test-decorator></test-decorator>' +
|
||||||
'<p><span style="white-space: pre-wrap;">After</span></p>',
|
'<p>After</p>',
|
||||||
initialHtml: '<p>Before</p><p>After</p>',
|
initialHtml: '<p>Before</p><p>After</p>',
|
||||||
selectionOffset: 1,
|
selectionOffset: 1,
|
||||||
selectionPath: [],
|
selectionPath: [],
|
||||||
@ -126,7 +126,7 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
{
|
{
|
||||||
_: 'insert with selection on root end',
|
_: 'insert with selection on root end',
|
||||||
expectedHtml:
|
expectedHtml:
|
||||||
'<p><span style="white-space: pre-wrap;">Before</span></p>' +
|
'<p>Before</p>' +
|
||||||
'<test-decorator></test-decorator>',
|
'<test-decorator></test-decorator>',
|
||||||
initialHtml: '<p>Before</p>',
|
initialHtml: '<p>Before</p>',
|
||||||
selectionOffset: 1,
|
selectionOffset: 1,
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
## Main Todo
|
## Main Todo
|
||||||
|
|
||||||
- Mac: Shortcut support via command.
|
- Mac: Shortcut support via command.
|
||||||
- Update toolbar overflows to match existing editor, incl. direction dynamic controls
|
|
||||||
|
|
||||||
## Secondary Todo
|
## Secondary Todo
|
||||||
|
|
||||||
@ -16,9 +15,9 @@
|
|||||||
- Table caption text support
|
- 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)
|
- 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
|
- Deep check of translation coverage
|
||||||
|
- About button & view
|
||||||
|
- Mobile display and handling
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
- Editor theme classes remain on items after export
|
- List selection can get lost on nesting/unnesting
|
||||||
- List selection can get lost on nesting/unnesting
|
|
||||||
- Content not properly saving on new pages
|
|
@ -163,6 +163,10 @@ export class EditorUIManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDefaultDirection(): 'rtl' | 'ltr' {
|
||||||
|
return this.getContext().options.textDirection === 'rtl' ? 'rtl' : 'ltr';
|
||||||
|
}
|
||||||
|
|
||||||
protected updateContextToolbars(update: EditorUiStateUpdate): void {
|
protected updateContextToolbars(update: EditorUiStateUpdate): void {
|
||||||
for (let i = this.activeContextToolbars.length - 1; i >= 0; i--) {
|
for (let i = this.activeContextToolbars.length - 1; i >= 0; i--) {
|
||||||
const toolbar = this.activeContextToolbars[i];
|
const toolbar = this.activeContextToolbars[i];
|
||||||
|
@ -32,7 +32,7 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, scro
|
|||||||
manager.setContext(context);
|
manager.setContext(context);
|
||||||
|
|
||||||
// Create primary toolbar
|
// Create primary toolbar
|
||||||
manager.setToolbar(getMainEditorFullToolbar());
|
manager.setToolbar(getMainEditorFullToolbar(context));
|
||||||
|
|
||||||
// Register modals
|
// Register modals
|
||||||
for (const key of Object.keys(modals)) {
|
for (const key of Object.keys(modals)) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {EditorButton} from "./framework/buttons";
|
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 {EditorFormatMenu} from "./framework/blocks/format-menu";
|
||||||
import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
|
import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
|
||||||
import {EditorDropdownButton} from "./framework/blocks/dropdown-button";
|
import {EditorDropdownButton} from "./framework/blocks/dropdown-button";
|
||||||
@ -80,7 +80,10 @@ import {el} from "../utils/dom";
|
|||||||
import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
|
import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
|
||||||
import {EditorSeparator} from "./framework/blocks/separator";
|
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', [
|
return new EditorSimpleClassContainer('editor-toolbar-main', [
|
||||||
|
|
||||||
// History state
|
// History state
|
||||||
@ -124,17 +127,17 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
|||||||
]),
|
]),
|
||||||
|
|
||||||
// Alignment
|
// Alignment
|
||||||
new EditorOverflowContainer(6, [ // TODO - Dynamic
|
new EditorOverflowContainer(6, [
|
||||||
new EditorButton(alignLeft),
|
new EditorButton(alignLeft),
|
||||||
new EditorButton(alignCenter),
|
new EditorButton(alignCenter),
|
||||||
new EditorButton(alignRight),
|
new EditorButton(alignRight),
|
||||||
new EditorButton(alignJustify),
|
new EditorButton(alignJustify),
|
||||||
new EditorButton(directionLTR), // TODO - Dynamic
|
inRtlMode ? new EditorButton(directionLTR) : null,
|
||||||
new EditorButton(directionRTL), // TODO - Dynamic
|
inRtlMode ? new EditorButton(directionRTL) : null,
|
||||||
]),
|
].filter(x => x !== null)),
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
new EditorOverflowContainer(5, [
|
new EditorOverflowContainer(3, [
|
||||||
new EditorButton(bulletList),
|
new EditorButton(bulletList),
|
||||||
new EditorButton(numberList),
|
new EditorButton(numberList),
|
||||||
new EditorButton(taskList),
|
new EditorButton(taskList),
|
||||||
|
@ -27,6 +27,7 @@ body.editor-is-fullscreen {
|
|||||||
}
|
}
|
||||||
.editor-content-area {
|
.editor-content-area {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
padding-block: 1rem;
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
@ -136,7 +137,6 @@ body.editor-is-fullscreen {
|
|||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.15);
|
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.15);
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
min-width: 120px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
@ -35,12 +35,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include larger-than($xxl) {
|
@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-edit-toolbar,
|
||||||
.page-editor-wysiwyg .page-editor-page-area {
|
.page-editor-wysiwyg .page-editor-page-area {
|
||||||
max-width: 1140px;
|
max-width: 1140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-editor-wysiwyg .floating-toolbox {
|
.page-editor-wysiwyg .floating-toolbox,
|
||||||
|
.page-editor-wysiwyg2024 .floating-toolbox {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user