Skip to content

Commit

Permalink
Improved behavior of shift-enter and br tags in lexical components. (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
cathysarisky authored Nov 20, 2024
1 parent e31b04a commit 5652bc6
Show file tree
Hide file tree
Showing 12 changed files with 523 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export function HeaderCard({alignment,
{/* Subheading */}
{<KoenigNestedEditor
dataTestId="header-subheader-editor"
defaultKoenigEnterBehaviour={true}
hasSettingsPanel={true}
initialEditor={subheaderTextEditor}
initialEditorState={subheaderTextEditorInitialState}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ export function SignupCard({alignment,
{/* Disclaimer */}
<KoenigNestedEditor
dataTestId="signup-disclaimer-editor"
defaultKoenigEnterBehaviour={true}
hasSettingsPanel={true}
initialEditor={disclaimerTextEditor}
initialEditorState={disclaimerTextEditorInitialState}
Expand Down
2 changes: 1 addition & 1 deletion packages/koenig-lexical/src/nodes/HeaderNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class HeaderNode extends BaseHeaderNode {
if (this.__headerTextEditor) {
this.__headerTextEditor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(this.__headerTextEditor, null);
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true, allowBr: true});
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
json.header = cleanedHtml;
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/koenig-lexical/src/nodes/ProductNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ export class ProductNode extends BaseProductNode {
if (this.__productTitleEditor) {
this.__productTitleEditor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(this.__productTitleEditor, null);
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true});
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
json.productTitle = cleanedHtml;
});
}
if (this.__productDescriptionEditor) {
this.__productDescriptionEditor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(this.__productDescriptionEditor, null);
const cleanedHtml = cleanBasicHtml(html, {allowBr: true});
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
json.productDescription = cleanedHtml;
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/koenig-lexical/src/nodes/SignupNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ export class SignupNode extends BaseSignupNode {
if (this.__disclaimerTextEditor) {
this.__disclaimerTextEditor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(this.__disclaimerTextEditor, null);
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true, allowBr: true});
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
json.disclaimer = cleanedHtml;
});
}

if (this.__headerTextEditor) {
this.__headerTextEditor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(this.__headerTextEditor, null);
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true, allowBr: true});
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
json.header = cleanedHtml;
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/koenig-lexical/src/nodes/ToggleNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class ToggleNode extends BaseToggleNode {
if (this.__headingEditor) {
this.__headingEditor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(this.__headingEditor, null);
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: true});
const cleanedHtml = cleanBasicHtml(html, {firstChildInnerContent: false, allowBr: true});
json.heading = cleanedHtml;
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/koenig-lexical/src/utils/nested-editors.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function populateNestedEditor(node, editorProperty, html) {
const nestedEditor = node[editorProperty];
const editorState = generateEditorState({
editor: nestedEditor,
initialHtml: html
initialHtml: `<p>${html}</p>`
});

nestedEditor.setEditorState(editorState, {tag: 'history-merge'}); // use history merge to prevent undo clearing the initial state
Expand Down
80 changes: 80 additions & 0 deletions packages/koenig-lexical/test/e2e/cards/header-card.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,45 @@ test.describe('Header card V2', () => {
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"]').nth(0)).toHaveText('hello world');
});

test('can import serialized header card nodes with br', async function () {
const contentParam = encodeURIComponent(JSON.stringify({
root: {
children: [{
version: 2,
type: 'header',
size: 'small',
style: 'image',
buttonEnabled: false,
buttonUrl: '',
buttonText: '',
header: '<span>hello world</span><br /><span>byebye world</span>',
subheader: '<span>hello sub</span><br /><span>byebye sub</span>',
backgroundImageSrc: 'blob:http://localhost:5173/fa0956a8-5fb4-4732-9368-18f9d6d8d25a',
alignment: 'left',
buttonColor: '#ffffff',
buttonTextColor: '#000000',
backgroundColor: 'accent',
textColor: '#ffffff',
swapped: false
}],
direction: null,
format: '',
indent: 0,
type: 'root',
version: 1
}
}));

await initialize({page, uri: `/#/?content=${contentParam}`});
await page.waitForSelector('[data-kg-card="header"]');
await page.waitForSelector('[data-kg-card="header"] [data-kg="editor"]');
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"] p span').nth(0)).toHaveText('hello world');
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"] p br').nth(0)).toBeAttached();
await expect(page.locator('[data-kg-card="header"] [data-kg="editor"] p span').nth(1)).toHaveText('byebye world');
await expect(page.getByTestId('header-subheader-editor').locator('p span').nth(0)).toHaveText('hello sub');
await expect(page.getByTestId('header-subheader-editor').locator('p br').nth(0)).toBeAttached();
await expect(page.getByTestId('header-subheader-editor').locator('p span').nth(1)).toHaveText('byebye sub');
});
test('renders header card node', async function () {
await createHeaderCard({page, version: 2});

Expand All @@ -460,6 +499,47 @@ test.describe('Header card V2', () => {
const firstEditor = page.locator('[data-kg-card="header"] [data-kg="editor"]').nth(0);
await expect(firstEditor).toHaveText('Hello world');
});
test('can add a shift-enter to header and subheader', async function () {
await createHeaderCard({page, version: 2});

await page.keyboard.type('Hello world');
await page.keyboard.press('Shift+Enter');
await page.keyboard.type('This is second line');
await page.keyboard.press('Enter');
await page.keyboard.type('Hello subheader');
await page.keyboard.press('Shift+Enter');
await page.keyboard.type('This is second subheader');
await page.keyboard.press('Escape');
await page.waitForSelector('[data-kg-card-editing="false"]');
await assertHTML(page, html`
<div
contenteditable="false"
role="textbox"
spellcheck="true"
data-lexical-editor="true"
aria-autocomplete="none"
aria-readonly="true">
<p dir="ltr"><span data-lexical-text="true">Hello world</span>
<br />
<span data-lexical-text="true">This is second line</span>
</p>
</div>`,
{selector: '[data-kg-card="header"] [data-kg="editor"]'});
await assertHTML(page, html`
<div
contenteditable="false"
role="textbox"
spellcheck="true"
data-lexical-editor="true"
aria-autocomplete="none"
aria-readonly="true">
<p dir="ltr"><span data-lexical-text="true">Hello subheader</span>
<br />
<span data-lexical-text="true">This is second subheader</span>
</p>
</div>`,
{selector: '[data-kg-card="header"] [data-testid="header-subheader-editor"] [data-kg="editor"]'});
});

test('can edit sub header', async function () {
await createHeaderCard({page, version: 2});
Expand Down
193 changes: 193 additions & 0 deletions packages/koenig-lexical/test/e2e/cards/product-card.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,99 @@ test.describe('Product card', async () => {
</div>
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
});
test('can import serialized product card nodes with a br', async function () {
const contentParam = encodeURIComponent(JSON.stringify({
root: {
children: [{
type: 'product',
productImageSrc: '/content/images/2022/11/koenig-lexical.jpg',
productTitle: '<span>This is <em>title</em></span><br /><span>Second line</span>',
productDescription: '<p dir="ltr"><span>Description</span><br /><span>Moar description</span></p>',
productUrl: 'https://google.com/',
productButton: 'Button',
productButtonEnabled: true,
productRatingEnabled: true,
productStarRating: 4
}],
direction: null,
format: '',
indent: 0,
type: 'root',
version: 1
}
}));

await initialize({page, uri: `/#/?content=${contentParam}`});

await assertHTML(page, html`
<div data-lexical-decorator="true" contenteditable="false">
<div
data-kg-card-editing="false"
data-kg-card-selected="false"
data-kg-card="product">
<div>
<div>
<img
alt="Product thumbnail"
src="/content/images/2022/11/koenig-lexical.jpg" />
</div>
<div>
<div>
<div>
<div data-kg="editor">
<div
contenteditable="false"
role="textbox"
spellcheck="true"
data-lexical-editor="true"
aria-autocomplete="none"
aria-readonly="true">
<p dir="ltr">
<span data-lexical-text="true">This is</span>
<em data-lexical-text="true">title</em>
<br />
<span data-lexical-text="true">Second line</span>
</p>
</div>
</div>
</div>
</div>
<div>
<button type="button"><svg></svg></button>
<button type="button"><svg></svg></button>
<button type="button"><svg></svg></button>
<button type="button"><svg></svg></button>
<button type="button"><svg></svg></button>
</div>
</div>
<div>
<div>
<div data-kg="editor">
<div
contenteditable="false"
role="textbox"
spellcheck="true"
data-lexical-editor="true"
aria-autocomplete="none"
aria-readonly="true">
<p dir="ltr">
<span data-lexical-text="true">Description</span>
<br />
<span data-lexical-text="true">Moar description</span>
</p>
</div>
</div>
</div>
</div>
<div>
<a href="https://google.com/"><span>Button</span></a>
</div>
</div>
<div></div>
</div>
</div>
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
});
test('renders product card node', async function () {
await focusEditor(page);
await insertCard(page, {cardName: 'product'});
Expand Down Expand Up @@ -454,6 +546,107 @@ test.describe('Product card', async () => {
<p><br /></p>
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
});
test('can handle new shift-enter in title and description', async () => {
await focusEditor(page);
await insertCard(page, {cardName: 'product'});

await page.keyboard.type('Test title');
await page.keyboard.press('Shift+Enter');
await page.keyboard.type('Second line of title');

await page.keyboard.press('Enter');
await page.keyboard.type('Test description');
await page.keyboard.press('Shift+Enter');
await page.keyboard.type('Second line of description');
await assertHTML(page, html`
<div data-lexical-decorator="true" contenteditable="false">
<div
data-kg-card-editing="true"
data-kg-card-selected="true"
data-kg-card="product">
<div>
<div>
<div>
<div>
<button name="placeholder-button" type="button">
<svg></svg>
<p>Click to select a product image</p>
</button>
</div>
</div>
<form>
<input
accept="image/gif,image/jpg,image/jpeg,image/png,image/svg+xml,image/webp"
hidden=""
name="image-input"
type="file" />
</form>
</div>
<div>
<div>
<div>
<div data-kg="editor">
<div
contenteditable="true"
role="textbox"
spellcheck="true"
data-lexical-editor="true"
>
<p dir="ltr">
<span data-lexical-text="true">Test title</span>
<br />
<span data-lexical-text="true">Second line of title</span>
</p>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div data-kg="editor">
<div
contenteditable="true"
role="textbox"
spellcheck="true"
data-lexical-editor="true"
>
<p dir="ltr"><span data-lexical-text="true">Test description</span>
<br />
<span data-lexical-text="true">Second line of description</span>
</p>
</div>
</div>
</div>
</div>
</div>
<div>
<div draggable="true">
<label>
<div><div>Rating</div></div>
<div>
<label id="product-rating-toggle">
<input type="checkbox" />
<div></div>
</label>
</div>
</label>
<label>
<div><div>Button</div></div>
<div>
<label id="product-button-toggle">
<input type="checkbox" />
<div></div>
</label>
</div>
</label>
</div>
</div>
</div>
</div>
<p><br /></p>
`, {ignoreCardToolbarContents: true, ignoreInnerSVG: true});
});
});

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

0 comments on commit 5652bc6

Please sign in to comment.