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 editable Sponsored Label to CTA Card #1437

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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 @@ -14,6 +14,7 @@ export class CallToActionNode extends generateDecoratorNode({
{name: 'buttonColor', default: ''},
{name: 'buttonTextColor', default: ''},
{name: 'hasSponsorLabel', default: true},
{name: 'sponsorLabel', default: '<p><span style="white-space: pre-wrap;">SPONSORED</span></p>'},
{name: 'backgroundColor', default: 'grey'},
{name: 'hasImage', default: false},
{name: 'imageUrl', default: ''}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function ctaCardTemplate(dataset) {
` : ''}
${dataset.hasSponsorLabel ? `
<div class="kg-sponsor-label">
Sponsored
${dataset.sponsorLabel}
</div>
` : ''}
</div>
Expand Down Expand Up @@ -48,7 +48,7 @@ function emailCTATemplate(dataset) {
` : ''}
${dataset.hasSponsorLabel ? `
<div class="sponsor-label" style="margin-top: 8px; font-size: 12px; color: #888;">
Sponsored
${dataset.sponsorLabel}
</div>
` : ''}
</div>
Expand All @@ -69,6 +69,7 @@ export function renderCallToActionNode(node, options = {}) {
buttonTextColor: node.buttonTextColor,
hasSponsorLabel: node.hasSponsorLabel,
backgroundColor: node.backgroundColor,
sponsorLabel: node.sponsorLabel,
hasImage: node.hasImage,
imageUrl: node.imageUrl,
textColor: node.textColor
Expand Down
15 changes: 14 additions & 1 deletion packages/kg-default-nodes/test/nodes/call-to-action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('CallToActionNode', function () {
dataset = {
layout: 'minimal',
textValue: 'This is a cool advertisement',
sponsorLabel: '<p><span style="white-space: pre-wrap;">SPONSORED</span></p>',
showButton: true,
buttonText: 'click me',
buttonUrl: 'http://blog.com/post1',
Expand Down Expand Up @@ -61,6 +62,7 @@ describe('CallToActionNode', function () {
callToActionNode.buttonColor.should.equal(dataset.buttonColor);
callToActionNode.buttonTextColor.should.equal(dataset.buttonTextColor);
callToActionNode.hasSponsorLabel.should.equal(dataset.hasSponsorLabel);
callToActionNode.sponsorLabel.should.equal(dataset.sponsorLabel);
callToActionNode.backgroundColor.should.equal(dataset.backgroundColor);
callToActionNode.hasImage.should.equal(dataset.hasImage);
callToActionNode.imageUrl.should.equal(dataset.imageUrl);
Expand Down Expand Up @@ -90,6 +92,10 @@ describe('CallToActionNode', function () {
callToActionNode.buttonUrl = 'http://blog.com/post1';
callToActionNode.buttonUrl.should.equal('http://blog.com/post1');

callToActionNode.sponsorLabel.should.equal('<p><span style="white-space: pre-wrap;">SPONSORED</span></p>');
callToActionNode.sponsorLabel = 'This post is brought to you by our sponsors';
callToActionNode.sponsorLabel.should.equal('This post is brought to you by our sponsors');

callToActionNode.buttonColor.should.equal('');
callToActionNode.buttonColor = 'red';
callToActionNode.buttonColor.should.equal('red');
Expand Down Expand Up @@ -184,11 +190,13 @@ describe('CallToActionNode', function () {
buttonUrl: 'http://someblog.com/somepost',
hasImage: true,
hasSponsorLabel: true,
sponsorLabel: '<p>Sponsored by</p>',
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>'
};

const callToActionNode = new CallToActionNode(dataset);
const {element} = callToActionNode.exportDOM(exportOptions);

Expand All @@ -200,7 +208,7 @@ describe('CallToActionNode', function () {
html.should.containEql('http://someblog.com/somepost');
html.should.containEql('/content/images/2022/11/koenig-lexical.jpg');// because hasImage is true
html.should.containEql('This is a new CTA Card.');
html.should.containEql('Sponsored'); // because hasSponsorLabel is true
html.should.containEql('Sponsored by'); // because hasSponsorLabel is true
html.should.containEql('cta-card');
}));

Expand All @@ -214,6 +222,7 @@ describe('CallToActionNode', function () {
buttonUrl: 'http://someblog.com/somepost',
hasImage: true,
hasSponsorLabel: true,
sponsorLabel: '<p><span style="white-space: pre-wrap;">SPONSORED</span></p>',
imageUrl: '/content/images/2022/11/koenig-lexical.jpg',
layout: 'minimal',
showButton: true,
Expand All @@ -228,6 +237,7 @@ describe('CallToActionNode', function () {
html.should.containEql('background-color: #F0F0F0');
html.should.containEql('Get access now');
html.should.containEql('http://someblog.com/somepost');
html.should.containEql('<p><span style="white-space: pre-wrap;">SPONSORED</span></p>'); // because hasSponsorLabel is true
html.should.containEql('/content/images/2022/11/koenig-lexical.jpg'); // because hasImage is true
html.should.containEql('This is a new CTA Card via email.');
}));
Expand Down Expand Up @@ -268,6 +278,7 @@ describe('CallToActionNode', function () {
buttonUrl: 'http://someblog.com/somepost',
hasImage: true,
hasSponsorLabel: true,
sponsorLabel: '<p>This post is brought to you by our sponsors</p>',
imageUrl: '/content/images/2022/11/koenig-lexical.jpg',
layout: 'minimal',
showButton: true,
Expand All @@ -286,6 +297,7 @@ describe('CallToActionNode', function () {
buttonUrl: 'http://someblog.com/somepost',
hasImage: true,
hasSponsorLabel: true,
sponsorLabel: '<p>This post is brought to you by our sponsors</p>',
imageUrl: '/content/images/2022/11/koenig-lexical.jpg',
layout: 'minimal',
showButton: true,
Expand Down Expand Up @@ -316,6 +328,7 @@ describe('CallToActionNode', function () {
buttonUrl: 'http://someblog.com/somepost',
hasImage: true,
hasSponsorLabel: true,
sponsorLabel: '<p>This post is brought to you by our sponsors</p>',
imageUrl: '/content/images/2022/11/koenig-lexical.jpg',
layout: 'minimal',
showButton: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const KoenigComposableEditor = ({
inheritStyles = false,
isSnippetsEnabled = true,
hiddenFormats = [],
useDefaultClasses = true,
dataTestId
}) => {
const {historyState} = useSharedHistoryContext();
Expand Down Expand Up @@ -89,14 +90,14 @@ const KoenigComposableEditor = ({
return (
<div
ref={onWrapperRef}
className={`koenig-lexical ${inheritStyles ? 'kg-inherit-styles' : ''} ${darkMode ? 'dark' : ''} ${className}`}
className={`${useDefaultClasses ? 'koenig-lexical' : ''} ${inheritStyles ? 'kg-inherit-styles' : ''} ${darkMode ? 'dark' : ''} ${className}`}
data-koenig-dnd-disabled={!isDragEnabled}
data-testid={dataTestId}
>
<RichTextPlugin
contentEditable={
<div ref={onContentEditableRef} data-kg="editor">
<ContentEditable className="kg-prose" readOnly={readOnly} />
<ContentEditable className={useDefaultClasses ? 'kg-prose' : ''} readOnly={readOnly} />
</div>
}
ErrorBoundary={KoenigErrorBoundary}
Expand Down
4 changes: 4 additions & 0 deletions packages/koenig-lexical/src/components/KoenigNestedEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Placeholder = ({text = 'Type here', className = ''}) => {
const KoenigNestedEditor = ({
initialEditor,
initialEditorState,
initialTheme,
nodes = 'basic',
placeholderText = '',
textClassName = '',
Expand All @@ -25,6 +26,7 @@ const KoenigNestedEditor = ({
hasSettingsPanel = false,
defaultKoenigEnterBehaviour = false,
hiddenFormats = [],
useDefaultClasses = true,
dataTestId,
children
}) => {
Expand All @@ -36,6 +38,7 @@ const KoenigNestedEditor = ({
initialEditor={initialEditor}
initialEditorState={initialEditorState}
initialNodes={initialNodes}
initialTheme={initialTheme}
>
<KoenigComposableEditor
className={textClassName}
Expand All @@ -45,6 +48,7 @@ const KoenigNestedEditor = ({
isDragEnabled={false}
markdownTransformers={markdownTransformers}
placeholder={<Placeholder className={placeholderClassName} text={placeholderText} />}
useDefaultClasses={useDefaultClasses}
>
{singleParagraph && <RestrictContentPlugin paragraphs={1} />}

Expand Down
50 changes: 38 additions & 12 deletions packages/koenig-lexical/src/components/ui/cards/CtaCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import PropTypes from 'prop-types';
import React, {useState} from 'react';
import ReplacementStringsPlugin from '../../../plugins/ReplacementStringsPlugin';
import clsx from 'clsx';
import defaultTheme from '../../../themes/default';
import {Button} from '../Button';
import {ButtonGroupSetting, ColorOptionSetting, ColorPickerSetting, InputSetting, InputUrlSetting, MediaUploadSetting, SettingsPanel, ToggleSetting} from '../SettingsPanel';
import {ReadOnlyOverlay} from '../ReadOnlyOverlay';
import {RestrictContentPlugin} from '../../../index.js';
import {getAccentColor} from '../../../utils/getAccentColor';
import {textColorForBackgroundColor} from '@tryghost/color-utils';

Expand All @@ -21,6 +23,11 @@ export const CTA_COLORS = {
red: 'bg-red/10 border-transparent'
};

const sponsoredLabelTheme = {
...defaultTheme,
link: 'text-accent'
};

export const ctaColorPicker = [
{
label: 'None',
Expand Down Expand Up @@ -68,6 +75,8 @@ export function CtaCard({
hasSponsorLabel,
htmlEditor,
htmlEditorInitialState,
sponsorLabelHtmlEditor,
sponsorLabelHtmlEditorInitialState,
Comment on lines +78 to +79
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add default values for new props.

The new props sponsorLabelHtmlEditor and sponsorLabelHtmlEditorInitialState are correctly added to PropTypes but are missing from defaultProps. This could cause issues if the props are not provided.

Add the following to defaultProps:

 CtaCard.defaultProps = {
     // ... existing props ...
+    sponsorLabelHtmlEditor: null,
+    sponsorLabelHtmlEditorInitialState: null,
     onRemoveMedia: () => {}
 };

Also applies to: 356-357

imageSrc,
isEditing,
layout,
Expand Down Expand Up @@ -225,13 +234,28 @@ export function CtaCard({
'pb-3': color === 'none' && hasSponsorLabel
}
)} data-cta-layout={layout}>

{/* Sponsor label */}
{hasSponsorLabel && (
<div className={clsx(
'not-kg-prose py-3',
'py-3',
{'mx-6': color !== 'none'}
)}>
<p className="font-sans text-2xs font-semibold uppercase leading-8 tracking-normal text-grey-900/40 dark:text-grey-100/40" data-testid="sponsor-label">Sponsored</p>
<KoenigNestedEditor
autoFocus={true}
dataTestId={'sponsor-label-editor'}
hasSettingsPanel={true}
initialEditor={sponsorLabelHtmlEditor}
initialEditorState={sponsorLabelHtmlEditorInitialState}
initialTheme={sponsoredLabelTheme}
nodes='basic'
textClassName={clsx(
'not-kg-prose w-full whitespace-normal font-sans !text-xs font-semibold uppercase leading-8 tracking-normal text-grey-900/40 dark:text-grey-100/40'
)}
useDefaultClasses={false}
>
<RestrictContentPlugin allowBr={false} paragraphs={1} />
</KoenigNestedEditor>
</div>
)}

Expand All @@ -247,13 +271,13 @@ export function CtaCard({
'block',
layout === 'immersive' ? 'w-full' : 'w-16 shrink-0'
)}>
<img
alt="Placeholder"
<img
alt="Placeholder"
className={clsx(
layout === 'immersive' ? 'h-auto w-full' : 'aspect-square w-16 object-cover',
'rounded-md'
)}
src={imageSrc}
)}
src={imageSrc}
/>
</div>
)}
Expand All @@ -279,10 +303,10 @@ export function CtaCard({
{/* Button */}
{ (showButton && (isEditing || (buttonText && buttonUrl))) &&
<div data-test-cta-button-current-url={buttonUrl}>
<Button
color={'accent'}
dataTestId="cta-button"
placeholder="Add button text"
<Button
color={'accent'}
dataTestId="cta-button"
placeholder="Add button text"
size={layout === 'immersive' ? 'medium' : 'small'}
style={buttonColor ? {
backgroundColor: buttonColor === 'accent' ? 'var(--accent-color)' : buttonColor,
Expand Down Expand Up @@ -322,7 +346,7 @@ CtaCard.propTypes = {
buttonColor: PropTypes.string,
buttonTextColor: PropTypes.string,
color: PropTypes.oneOf(['none', 'grey', 'white', 'blue', 'green', 'yellow', 'red']),
hasSponsorLabel: PropTypes.bool,
hasSponsorLabel: PropTypes.bool,
imageSrc: PropTypes.string,
isEditing: PropTypes.bool,
layout: PropTypes.oneOf(['minimal', 'immersive']),
Expand All @@ -338,7 +362,9 @@ CtaCard.propTypes = {
handleButtonColor: PropTypes.func,
onFileChange: PropTypes.func,
setFileInputRef: PropTypes.func,
onRemoveMedia: PropTypes.func
onRemoveMedia: PropTypes.func,
sponsorLabelHtmlEditor: PropTypes.object,
sponsorLabelHtmlEditorInitialState: PropTypes.object
};

CtaCard.defaultProps = {
Expand Down
18 changes: 18 additions & 0 deletions packages/koenig-lexical/src/nodes/CallToActionNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const INSERT_CTA_COMMAND = createCommand();
export class CallToActionNode extends BaseCallToActionNode {
__ctaHtmlEditor;
__ctaHtmlEditorInitialState;
__sponsorLabelHtmlEditor;
__sponsorLabelHtmlEditorInitialState;

static kgMenu = {
label: 'Call to Action',
Expand Down Expand Up @@ -40,11 +42,15 @@ export class CallToActionNode extends BaseCallToActionNode {

// set up nested editor instances
setupNestedEditor(this, '__ctaHtmlEditor', {editor: dataset.ctaHtmlEditor, nodes: BASIC_NODES});
setupNestedEditor(this, '__sponsorLabelHtmlEditor', {editor: dataset.sponsorLabelHtmlEditor, nodes: BASIC_NODES});

// populate nested editors on initial construction
if (!dataset.ctaHtmlEditor && dataset.textValue) {
populateNestedEditor(this, '__ctaHtmlEditor', `${dataset.textValue}`); // we serialize with no wrapper
}
if (!dataset.sponsorLabelHtmlEditor) {
populateNestedEditor(this, '__sponsorLabelHtmlEditor', `${dataset.sponsorLabel || '<p><span style="white-space: pre-wrap;">SPONSORED</span></p>'}`);
}
}

getDataset() {
Expand All @@ -53,6 +59,8 @@ export class CallToActionNode extends BaseCallToActionNode {
const self = this.getLatest();
dataset.ctaHtmlEditor = self.__ctaHtmlEditor;
dataset.ctaHtmlEditorInitialState = self.__ctaHtmlEditorInitialState;
dataset.sponsorLabelHtmlEditor = self.__sponsorLabelHtmlEditor;
dataset.sponsorLabelHtmlEditorInitialState = self.__sponsorLabelHtmlEditorInitialState;

return dataset;
}
Expand All @@ -70,6 +78,14 @@ export class CallToActionNode extends BaseCallToActionNode {
});
}

if (this.__sponsorLabelHtmlEditor) {
this.__sponsorLabelHtmlEditor.getEditorState().read(() => {
const html = $generateHtmlFromNodes(this.__sponsorLabelHtmlEditor, null);
const cleanedHtml = cleanBasicHtml(html, {allowBr: false});
json.sponsorLabel = cleanedHtml;
});
}

return json;
}

Expand All @@ -93,6 +109,8 @@ export class CallToActionNode extends BaseCallToActionNode {
layout={this.layout}
nodeKey={this.getKey()}
showButton={this.showButton}
sponsorLabelHtmlEditor={this.__sponsorLabelHtmlEditor}
sponsorLabelHtmlEditorInitialState={this.__sponsorLabelHtmlEditorInitialState}
textValue={this.textValue}
/>
</KoenigCardWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const CallToActionNodeComponent = ({
buttonColor,
htmlEditor,
htmlEditorInitialState,
buttonTextColor
buttonTextColor,
sponsorLabelHtmlEditor,
sponsorLabelHtmlEditorInitialState
}) => {
const [editor] = useLexicalComposerContext();
const {isEditing, isSelected, setEditing} = React.useContext(CardContext);
Expand Down Expand Up @@ -135,6 +137,8 @@ export const CallToActionNodeComponent = ({
setEditing={setEditing}
setFileInputRef={ref => fileInputRef.current = ref}
showButton={showButton}
sponsorLabelHtmlEditor={sponsorLabelHtmlEditor}
sponsorLabelHtmlEditorInitialState={sponsorLabelHtmlEditorInitialState}
text={textValue}
updateButtonText={handleButtonTextChange}
updateButtonUrl={handleButtonUrlChange}
Expand Down
Loading
Loading