Skip to content

Commit 5652bc6

Browse files
authored
Improved behavior of shift-enter and br tags in lexical components. (#1387)
no ref Shift+enter behavior was stripped out in most cases when loading the serialized editor state, i.e. line breaks in things like the Product card title field were removed on re-loading a post. This commit expands the use cases for shift+enter support with the product card, toggle card, signup card title, and header card.
1 parent e31b04a commit 5652bc6

12 files changed

+523
-7
lines changed

packages/koenig-lexical/src/components/ui/cards/HeaderCard/v2/HeaderCard.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ export function HeaderCard({alignment,
314314
{/* Subheading */}
315315
{<KoenigNestedEditor
316316
dataTestId="header-subheader-editor"
317+
defaultKoenigEnterBehaviour={true}
317318
hasSettingsPanel={true}
318319
initialEditor={subheaderTextEditor}
319320
initialEditorState={subheaderTextEditorInitialState}

packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ export function SignupCard({alignment,
336336
{/* Disclaimer */}
337337
<KoenigNestedEditor
338338
dataTestId="signup-disclaimer-editor"
339+
defaultKoenigEnterBehaviour={true}
339340
hasSettingsPanel={true}
340341
initialEditor={disclaimerTextEditor}
341342
initialEditorState={disclaimerTextEditorInitialState}

packages/koenig-lexical/src/nodes/HeaderNode.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class HeaderNode extends BaseHeaderNode {
7676
if (this.__headerTextEditor) {
7777
this.__headerTextEditor.getEditorState().read(() => {
7878
const html = $generateHtmlFromNodes(this.__headerTextEditor, null);
79-
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true, allowBr: true});
79+
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
8080
json.header = cleanedHtml;
8181
});
8282
}

packages/koenig-lexical/src/nodes/ProductNode.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ export class ProductNode extends BaseProductNode {
6868
if (this.__productTitleEditor) {
6969
this.__productTitleEditor.getEditorState().read(() => {
7070
const html = $generateHtmlFromNodes(this.__productTitleEditor, null);
71-
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true});
71+
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
7272
json.productTitle = cleanedHtml;
7373
});
7474
}
7575
if (this.__productDescriptionEditor) {
7676
this.__productDescriptionEditor.getEditorState().read(() => {
7777
const html = $generateHtmlFromNodes(this.__productDescriptionEditor, null);
78-
const cleanedHtml = cleanBasicHtml(html, {allowBr: true});
78+
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
7979
json.productDescription = cleanedHtml;
8080
});
8181
}

packages/koenig-lexical/src/nodes/SignupNode.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@ export class SignupNode extends BaseSignupNode {
7171
if (this.__disclaimerTextEditor) {
7272
this.__disclaimerTextEditor.getEditorState().read(() => {
7373
const html = $generateHtmlFromNodes(this.__disclaimerTextEditor, null);
74-
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true, allowBr: true});
74+
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
7575
json.disclaimer = cleanedHtml;
7676
});
7777
}
7878

7979
if (this.__headerTextEditor) {
8080
this.__headerTextEditor.getEditorState().read(() => {
8181
const html = $generateHtmlFromNodes(this.__headerTextEditor, null);
82-
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true, allowBr: true});
82+
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
8383
json.header = cleanedHtml;
8484
});
8585
}

packages/koenig-lexical/src/nodes/ToggleNode.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class ToggleNode extends BaseToggleNode {
6767
if (this.__headingEditor) {
6868
this.__headingEditor.getEditorState().read(() => {
6969
const html = $generateHtmlFromNodes(this.__headingEditor, null);
70-
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true});
70+
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
7171
json.heading = cleanedHtml;
7272
});
7373
}

packages/koenig-lexical/src/utils/nested-editors.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function populateNestedEditor(node, editorProperty, html) {
4242
const nestedEditor = node[editorProperty];
4343
const editorState = generateEditorState({
4444
editor: nestedEditor,
45-
initialHtml: html
45+
initialHtml: `<p>${html}</p>`
4646
});
4747

4848
nestedEditor.setEditorState(editorState, {tag: 'history-merge'}); // use history merge to prevent undo clearing the initial state

packages/koenig-lexical/test/e2e/cards/header-card.test.js

+80
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,45 @@ test.describe('Header card V2', () => {
441441
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"]').nth(0)).toHaveText('hello world');
442442
});
443443

444+
test('can import serialized header card nodes with br', async function () {
445+
const contentParam = encodeURIComponent(JSON.stringify({
446+
root: {
447+
children: [{
448+
version: 2,
449+
type: 'header',
450+
size: 'small',
451+
style: 'image',
452+
buttonEnabled: false,
453+
buttonUrl: '',
454+
buttonText: '',
455+
header: '<span>hello world</span><br /><span>byebye world</span>',
456+
subheader: '<span>hello sub</span><br /><span>byebye sub</span>',
457+
backgroundImageSrc: 'blob:http://localhost:5173/fa0956a8-5fb4-4732-9368-18f9d6d8d25a',
458+
alignment: 'left',
459+
buttonColor: '#ffffff',
460+
buttonTextColor: '#000000',
461+
backgroundColor: 'accent',
462+
textColor: '#ffffff',
463+
swapped: false
464+
}],
465+
direction: null,
466+
format: '',
467+
indent: 0,
468+
type: 'root',
469+
version: 1
470+
}
471+
}));
472+
473+
await initialize({page, uri: `/#/?content=${contentParam}`});
474+
await page.waitForSelector('[data-kg-card="header"]');
475+
await page.waitForSelector('[data-kg-card="header"] [data-kg="editor"]');
476+
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"] p span').nth(0)).toHaveText('hello world');
477+
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"] p br').nth(0)).toBeAttached();
478+
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"] p span').nth(1)).toHaveText('byebye world');
479+
await expect(page.getByTestId('header-subheader-editor').locator('p span').nth(0)).toHaveText('hello sub');
480+
await expect(page.getByTestId('header-subheader-editor').locator('p br').nth(0)).toBeAttached();
481+
await expect(page.getByTestId('header-subheader-editor').locator('p span').nth(1)).toHaveText('byebye sub');
482+
});
444483
test('renders header card node', async function () {
445484
await createHeaderCard({page, version: 2});
446485

@@ -460,6 +499,47 @@ test.describe('Header card V2', () => {
460499
const firstEditor = page.locator('[data-kg-card="header"] [data-kg="editor"]').nth(0);
461500
await expect(firstEditor).toHaveText('Hello world');
462501
});
502+
test('can add a shift-enter to header and subheader', async function () {
503+
await createHeaderCard({page, version: 2});
504+
505+
await page.keyboard.type('Hello world');
506+
await page.keyboard.press('Shift+Enter');
507+
await page.keyboard.type('This is second line');
508+
await page.keyboard.press('Enter');
509+
await page.keyboard.type('Hello subheader');
510+
await page.keyboard.press('Shift+Enter');
511+
await page.keyboard.type('This is second subheader');
512+
await page.keyboard.press('Escape');
513+
await page.waitForSelector('[data-kg-card-editing="false"]');
514+
await assertHTML(page, html`
515+
<div
516+
contenteditable="false"
517+
role="textbox"
518+
spellcheck="true"
519+
data-lexical-editor="true"
520+
aria-autocomplete="none"
521+
aria-readonly="true">
522+
<p dir="ltr"><span data-lexical-text="true">Hello world</span>
523+
<br />
524+
<span data-lexical-text="true">This is second line</span>
525+
</p>
526+
</div>`,
527+
{selector: '[data-kg-card="header"] [data-kg="editor"]'});
528+
await assertHTML(page, html`
529+
<div
530+
contenteditable="false"
531+
role="textbox"
532+
spellcheck="true"
533+
data-lexical-editor="true"
534+
aria-autocomplete="none"
535+
aria-readonly="true">
536+
<p dir="ltr"><span data-lexical-text="true">Hello subheader</span>
537+
<br />
538+
<span data-lexical-text="true">This is second subheader</span>
539+
</p>
540+
</div>`,
541+
{selector: '[data-kg-card="header"] [data-testid="header-subheader-editor"] [data-kg="editor"]'});
542+
});
463543

464544
test('can edit sub header', async function () {
465545
await createHeaderCard({page, version: 2});

packages/koenig-lexical/test/e2e/cards/product-card.test.js

+193
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,99 @@ test.describe('Product card', async () => {
108108
</div>
109109
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
110110
});
111+
test('can import serialized product card nodes with a br', async function () {
112+
const contentParam = encodeURIComponent(JSON.stringify({
113+
root: {
114+
children: [{
115+
type: 'product',
116+
productImageSrc: '/content/images/2022/11/koenig-lexical.jpg',
117+
productTitle: '<span>This is <em>title</em></span><br /><span>Second line</span>',
118+
productDescription: '<p dir="ltr"><span>Description</span><br /><span>Moar description</span></p>',
119+
productUrl: 'https://google.com/',
120+
productButton: 'Button',
121+
productButtonEnabled: true,
122+
productRatingEnabled: true,
123+
productStarRating: 4
124+
}],
125+
direction: null,
126+
format: '',
127+
indent: 0,
128+
type: 'root',
129+
version: 1
130+
}
131+
}));
132+
133+
await initialize({page, uri: `/#/?content=${contentParam}`});
111134

135+
await assertHTML(page, html`
136+
<div data-lexical-decorator="true" contenteditable="false">
137+
<div
138+
data-kg-card-editing="false"
139+
data-kg-card-selected="false"
140+
data-kg-card="product">
141+
<div>
142+
<div>
143+
<img
144+
alt="Product thumbnail"
145+
src="/content/images/2022/11/koenig-lexical.jpg" />
146+
</div>
147+
<div>
148+
<div>
149+
<div>
150+
<div data-kg="editor">
151+
<div
152+
contenteditable="false"
153+
role="textbox"
154+
spellcheck="true"
155+
data-lexical-editor="true"
156+
aria-autocomplete="none"
157+
aria-readonly="true">
158+
<p dir="ltr">
159+
<span data-lexical-text="true">This is</span>
160+
<em data-lexical-text="true">title</em>
161+
<br />
162+
<span data-lexical-text="true">Second line</span>
163+
</p>
164+
</div>
165+
</div>
166+
</div>
167+
</div>
168+
<div>
169+
<button type="button"><svg></svg></button>
170+
<button type="button"><svg></svg></button>
171+
<button type="button"><svg></svg></button>
172+
<button type="button"><svg></svg></button>
173+
<button type="button"><svg></svg></button>
174+
</div>
175+
</div>
176+
<div>
177+
<div>
178+
<div data-kg="editor">
179+
<div
180+
contenteditable="false"
181+
role="textbox"
182+
spellcheck="true"
183+
data-lexical-editor="true"
184+
aria-autocomplete="none"
185+
aria-readonly="true">
186+
<p dir="ltr">
187+
<span data-lexical-text="true">Description</span>
188+
<br />
189+
<span data-lexical-text="true">Moar description</span>
190+
</p>
191+
</div>
192+
</div>
193+
</div>
194+
</div>
195+
<div>
196+
<a href="https://google.com/"><span>Button</span></a>
197+
</div>
198+
</div>
199+
<div></div>
200+
</div>
201+
</div>
202+
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
203+
});
112204
test('renders product card node', async function () {
113205
await focusEditor(page);
114206
await insertCard(page, {cardName: 'product'});
@@ -454,6 +546,107 @@ test.describe('Product card', async () => {
454546
<p><br /></p>
455547
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
456548
});
549+
test('can handle new shift-enter in title and description', async () => {
550+
await focusEditor(page);
551+
await insertCard(page, {cardName: 'product'});
552+
553+
await page.keyboard.type('Test title');
554+
await page.keyboard.press('Shift+Enter');
555+
await page.keyboard.type('Second line of title');
556+
557+
await page.keyboard.press('Enter');
558+
await page.keyboard.type('Test description');
559+
await page.keyboard.press('Shift+Enter');
560+
await page.keyboard.type('Second line of description');
561+
await assertHTML(page, html`
562+
<div data-lexical-decorator="true" contenteditable="false">
563+
<div
564+
data-kg-card-editing="true"
565+
data-kg-card-selected="true"
566+
data-kg-card="product">
567+
<div>
568+
<div>
569+
<div>
570+
<div>
571+
<button name="placeholder-button" type="button">
572+
<svg></svg>
573+
<p>Click to select a product image</p>
574+
</button>
575+
</div>
576+
</div>
577+
<form>
578+
<input
579+
accept="image/gif,image/jpg,image/jpeg,image/png,image/svg+xml,image/webp"
580+
hidden=""
581+
name="image-input"
582+
type="file" />
583+
</form>
584+
</div>
585+
<div>
586+
<div>
587+
<div>
588+
<div data-kg="editor">
589+
<div
590+
contenteditable="true"
591+
role="textbox"
592+
spellcheck="true"
593+
data-lexical-editor="true"
594+
>
595+
<p dir="ltr">
596+
<span data-lexical-text="true">Test title</span>
597+
<br />
598+
<span data-lexical-text="true">Second line of title</span>
599+
</p>
600+
</div>
601+
</div>
602+
</div>
603+
</div>
604+
</div>
605+
<div>
606+
<div>
607+
<div data-kg="editor">
608+
<div
609+
contenteditable="true"
610+
role="textbox"
611+
spellcheck="true"
612+
data-lexical-editor="true"
613+
>
614+
<p dir="ltr"><span data-lexical-text="true">Test description</span>
615+
<br />
616+
<span data-lexical-text="true">Second line of description</span>
617+
</p>
618+
</div>
619+
</div>
620+
</div>
621+
</div>
622+
</div>
623+
<div>
624+
<div draggable="true">
625+
<label>
626+
<div><div>Rating</div></div>
627+
<div>
628+
<label id="product-rating-toggle">
629+
<input type="checkbox" />
630+
<div></div>
631+
</label>
632+
</div>
633+
</label>
634+
<label>
635+
<div><div>Button</div></div>
636+
<div>
637+
<label id="product-button-toggle">
638+
<input type="checkbox" />
639+
<div></div>
640+
</label>
641+
</div>
642+
</label>
643+
</div>
644+
</div>
645+
</div>
646+
</div>
647+
<p><br /></p>
648+
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
649+
});
457650
});
458651

459652
async function uploadImg(page, src = 'large-image.png') {

0 commit comments

Comments
 (0)