Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added link button to CTA Card toolbar #1441

Merged
merged 3 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class CallToActionNode extends generateDecoratorNode({
{name: 'sponsorLabel', default: '<p><span style="white-space: pre-wrap;">SPONSORED</span></p>'},
{name: 'backgroundColor', default: 'grey'},
{name: 'hasImage', default: false},
{name: 'imageUrl', default: ''}
{name: 'imageUrl', default: ''},
{name: 'href', default: '', urlType: 'url'}
]
}) {
/* overrides */
Expand Down
12 changes: 10 additions & 2 deletions packages/kg-default-nodes/test/nodes/call-to-action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ describe('CallToActionNode', function () {
hasSponsorLabel: true,
backgroundColor: 'none',
hasImage: true,
imageUrl: 'http://blog.com/image1.jpg'
imageUrl: 'http://blog.com/image1.jpg',
href: ''
};
exportOptions = {
exportFormat: 'html',
Expand Down Expand Up @@ -67,6 +68,7 @@ describe('CallToActionNode', function () {
callToActionNode.hasImage.should.equal(dataset.hasImage);
callToActionNode.imageUrl.should.equal(dataset.imageUrl);
callToActionNode.visibility.should.deepEqual(utils.visibility.buildDefaultVisibility());
callToActionNode.href.should.equal(dataset.href);
}));

it('has setters for all properties', editorTest(function () {
Expand Down Expand Up @@ -120,6 +122,10 @@ describe('CallToActionNode', function () {
callToActionNode.imageUrl = 'http://blog.com/image1.jpg';
callToActionNode.imageUrl.should.equal('http://blog.com/image1.jpg');

callToActionNode.href.should.equal('');
callToActionNode.href = 'http://blog.com/post1';
callToActionNode.href.should.equal('http://blog.com/post1');

callToActionNode.visibility.should.deepEqual(utils.visibility.buildDefaultVisibility());
callToActionNode.visibility = {
web: {
Expand Down Expand Up @@ -307,7 +313,8 @@ describe('CallToActionNode', function () {
imageUrl: '/content/images/2022/11/koenig-lexical.jpg',
layout: 'minimal',
showButton: true,
textValue: '<p><span style="white-space: pre-wrap;">This is a new CTA Card.</span></p>'
textValue: '<p><span style="white-space: pre-wrap;">This is a new CTA Card.</span></p>',
href: ''
};
const callToActionNode = new CallToActionNode(dataset);
const json = callToActionNode.exportJSON();
Expand All @@ -327,6 +334,7 @@ describe('CallToActionNode', function () {
layout: 'minimal',
showButton: true,
textValue: '<p><span style="white-space: pre-wrap;">This is a new CTA Card.</span></p>',
href: '',
visibility: {
web: {
nonMember: true,
Expand Down
1 change: 1 addition & 0 deletions packages/koenig-lexical/src/nodes/CallToActionNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export class CallToActionNode extends BaseCallToActionNode {
buttonUrl={this.buttonUrl}
hasImage={this.hasImage}
hasSponsorLabel={this.hasSponsorLabel}
href={this.href}
htmlEditor={this.__ctaHtmlEditor}
htmlEditorInitialState={this.__ctaHtmlEditorInitialState}
imageUrl={this.imageUrl}
Expand Down
46 changes: 43 additions & 3 deletions packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import CardContext from '../context/CardContext';
import KoenigComposerContext from '../context/KoenigComposerContext.jsx';
import React, {useRef} from 'react';
import {$getNodeByKey} from 'lexical';
import {$createNodeSelection, $getNodeByKey, $setSelection} from 'lexical';
import {ActionToolbar} from '../components/ui/ActionToolbar.jsx';
import {CtaCard} from '../components/ui/cards/CtaCard';
import {LinkInput} from '../components/ui/LinkInput';
import {SnippetActionToolbar} from '../components/ui/SnippetActionToolbar.jsx';
import {ToolbarMenu, ToolbarMenuItem, ToolbarMenuSeparator} from '../components/ui/ToolbarMenu.jsx';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
Expand All @@ -24,13 +25,15 @@ export const CallToActionNodeComponent = ({
htmlEditor,
htmlEditorInitialState,
buttonTextColor,
href,
sponsorLabelHtmlEditor,
sponsorLabelHtmlEditorInitialState
}) => {
const [editor] = useLexicalComposerContext();
const {isEditing, isSelected, setEditing} = React.useContext(CardContext);
const {fileUploader, cardConfig} = React.useContext(KoenigComposerContext);
const [showSnippetToolbar, setShowSnippetToolbar] = React.useState(false);
const [showLink, setShowLink] = React.useState(false);

const {visibilityOptions, toggleVisibility} = useVisibilityToggle(editor, nodeKey, cardConfig);

Expand Down Expand Up @@ -115,6 +118,26 @@ export const CallToActionNodeComponent = ({
});
};

const reselectCTACard = () => {
editor.update(() => {
const nodeSelection = $createNodeSelection();
nodeSelection.add(nodeKey);
$setSelection(nodeSelection);
});
};

const cancelLinkAndReselect = () => {
setShowLink(false);
reselectCTACard();
};

const setHref = (newHref) => {
editor.update(() => {
const node = $getNodeByKey(nodeKey);
node.href = newHref;
});
};

React.useEffect(() => {
htmlEditor.setEditable(isEditing);
}, [isEditing, htmlEditor]);
Expand All @@ -136,7 +159,6 @@ export const CallToActionNodeComponent = ({
imageSrc={imageUrl}
imageUploader={imageUploader}
isEditing={isEditing}
isSelected={isSelected}
layout={layout}
setEditing={setEditing}
setFileInputRef={ref => fileInputRef.current = ref}
Expand All @@ -162,12 +184,30 @@ export const CallToActionNodeComponent = ({
<SnippetActionToolbar onClose={() => setShowSnippetToolbar(false)} />
</ActionToolbar>

<ActionToolbar
data-kg-card-toolbar="link"
isVisible={showLink}
>
<LinkInput
cancel={cancelLinkAndReselect}
href={href}
update={(_href) => {
setHref(_href);
cancelLinkAndReselect();
}}
/>
</ActionToolbar>

<ActionToolbar
data-kg-card-toolbar="button"
isVisible={isSelected && !isEditing}
isVisible={isSelected && !isEditing && !showSnippetToolbar && !showLink}
>
<ToolbarMenu>
<ToolbarMenuItem dataTestId="edit-button-card" icon="edit" isActive={false} label="Edit" onClick={handleToolbarEdit} />
<ToolbarMenuSeparator />
<ToolbarMenuItem icon="link" isActive={href || false} label="Link" onClick = {() => {
setShowLink(true);
}} />
<ToolbarMenuSeparator hide={!cardConfig.createSnippet} />
<ToolbarMenuItem
dataTestId="create-snippet"
Expand Down
20 changes: 20 additions & 0 deletions packages/koenig-lexical/test/e2e/cards/cta-card.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,4 +485,24 @@ test.describe('Call To Action Card', async () => {
await expect(page.getByTestId('visibility-toggle-email-freeMembers')).toBeChecked();
await expect(page.getByTestId('visibility-toggle-email-paidMembers')).not.toBeChecked();
});

test('CTA card toolbar has Link button and link input', async function () {
await focusEditor(page);
await insertCard(page, {cardName: 'call-to-action'});
await page.keyboard.press('Escape');
const toolbarLinkButton = await page.locator('[aria-label="Link"]');
await expect(toolbarLinkButton).toBeVisible();
await toolbarLinkButton.click();
await expect(page.getByTestId('link-input')).toBeVisible();
});

test('Settings panel does not show when using link editor', async function () {
await focusEditor(page);
await insertCard(page, {cardName: 'call-to-action'});
await page.keyboard.press('Escape');
const toolbarLinkButton = await page.locator('[aria-label="Link"]');
await toolbarLinkButton.click();
await expect(page.getByTestId('link-input')).toBeVisible();
await expect(page.getByTestId('button-settings')).not.toBeVisible();
});
});
Loading