-
-
Notifications
You must be signed in to change notification settings - Fork 80
Updated html markup for CTA card frontend rendering #1442
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
Changes from 10 commits
57244a7
8eec344
37897fb
aece70e
059302d
ac64145
c5c512d
96df53b
19c7141
3eb9140
3777a50
31dcacd
b8ee167
d028e71
8e66d4f
dc8346b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,63 +3,142 @@ import {renderWithVisibility} from '../../utils/visibility'; | |||||||||||||||||
|
|
||||||||||||||||||
| // TODO - this is a placeholder for the cta card web template | ||||||||||||||||||
| function ctaCardTemplate(dataset) { | ||||||||||||||||||
| const backgroundAccent = dataset.backgroundColor === 'accent' ? 'kg-style-accent' : ''; | ||||||||||||||||||
| // Add validation for buttonColor | ||||||||||||||||||
| if (!dataset.buttonColor || !dataset.buttonColor.match(/^[a-zA-Z\d-]+|#([a-fA-F\d]{3}|[a-fA-F\d]{6})$/)) { | ||||||||||||||||||
| dataset.buttonColor = 'accent'; | ||||||||||||||||||
| } | ||||||||||||||||||
| const buttonAccent = dataset.buttonColor === 'accent' ? 'kg-style-accent' : ''; | ||||||||||||||||||
| const buttonStyle = dataset.buttonColor !== 'accent' ? `background-color: ${dataset.buttonColor};` : ''; | ||||||||||||||||||
|
|
||||||||||||||||||
| const buttonStyle = dataset.buttonColor === 'accent' | ||||||||||||||||||
| ? `style="color: ${dataset.buttonTextColor};"` | ||||||||||||||||||
| : `style="background-color: ${dataset.buttonColor}; color: ${dataset.buttonTextColor};"`; | ||||||||||||||||||
| return ` | ||||||||||||||||||
| <div class="cta-card ${backgroundAccent}" data-layout="${dataset.layout}" style="background-color: ${dataset.backgroundColor};"> | ||||||||||||||||||
| ${dataset.hasImage ? `<img src="${dataset.imageUrl}" alt="CTA Image">` : ''} | ||||||||||||||||||
| <div> | ||||||||||||||||||
| ${dataset.textValue} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ${dataset.showButton ? ` | ||||||||||||||||||
| <a href="${dataset.buttonUrl}" class="kg-cta-button ${buttonAccent}" | ||||||||||||||||||
| style="${buttonStyle} color: ${dataset.buttonTextColor};"> | ||||||||||||||||||
| ${dataset.buttonText} | ||||||||||||||||||
| </a> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| <div class="kg-card kg-cta-card kg-cta-bg-${dataset.backgroundColor} kg-cta-${dataset.layout}" data-layout="${dataset.layout}"> | ||||||||||||||||||
| ${dataset.hasSponsorLabel ? ` | ||||||||||||||||||
| <div class="kg-sponsor-label"> | ||||||||||||||||||
| <div class="kg-cta-sponsor-label"> | ||||||||||||||||||
| ${dataset.sponsorLabel} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| <div class="kg-cta-content"> | ||||||||||||||||||
| ${dataset.hasImage ? ` | ||||||||||||||||||
| <div class="kg-cta-image-container"> | ||||||||||||||||||
| <img src="${dataset.imageUrl}" alt="CTA Image"> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| <div class="kg-cta-content-inner"> | ||||||||||||||||||
| <div class="kg-cta-text"> | ||||||||||||||||||
| ${dataset.textValue} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ${dataset.showButton ? ` | ||||||||||||||||||
| <a href="${dataset.buttonUrl}" class="kg-cta-button ${buttonAccent}" | ||||||||||||||||||
| ${buttonStyle}> | ||||||||||||||||||
| ${dataset.buttonText} | ||||||||||||||||||
| </a> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| `; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // TODO - this is a placeholder for the email template | ||||||||||||||||||
| function emailCTATemplate(dataset) { | ||||||||||||||||||
| const buttonStyle = dataset.buttonColor !== 'accent' ? `background-color: ${dataset.buttonColor};` : ''; | ||||||||||||||||||
| const backgroundStyle = `background-color: ${dataset.backgroundColor};`; | ||||||||||||||||||
| const buttonStyle = dataset.buttonColor === 'accent' | ||||||||||||||||||
| ? `color: ${dataset.buttonTextColor};` | ||||||||||||||||||
| : `background-color: ${dataset.buttonColor}; color: ${dataset.buttonTextColor};`; | ||||||||||||||||||
|
|
||||||||||||||||||
| const renderContent = () => { | ||||||||||||||||||
| if (dataset.layout === 'minimal') { | ||||||||||||||||||
| return ` | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td class="kg-cta-content"> | ||||||||||||||||||
| <table border="0" cellpadding="0" cellspacing="0" width="100%" class="kg-cta-content-wrapper"> | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| ${dataset.hasImage ? ` | ||||||||||||||||||
| <td class="kg-cta-image-container" width="64"> | ||||||||||||||||||
| <img src="${dataset.imageUrl}" alt="CTA Image" class="kg-cta-image" width="64" height="64"> | ||||||||||||||||||
| </td> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| <td class="kg-cta-content-inner"> | ||||||||||||||||||
| <div class="kg-cta-text"> | ||||||||||||||||||
| ${dataset.textValue} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ${dataset.showButton ? ` | ||||||||||||||||||
| <a href="${dataset.buttonUrl}" | ||||||||||||||||||
| class="kg-cta-button ${dataset.buttonColor === 'accent' ? 'kg-style-accent' : ''}" | ||||||||||||||||||
| style="${buttonStyle}"> | ||||||||||||||||||
| ${dataset.buttonText} | ||||||||||||||||||
| </a> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| </table> | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| `; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return ` | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td class="kg-cta-content"> | ||||||||||||||||||
| <table border="0" cellpadding="0" cellspacing="0" width="100%" class="kg-cta-content-wrapper"> | ||||||||||||||||||
| ${dataset.hasImage ? ` | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td class="kg-cta-image-container"> | ||||||||||||||||||
| <table border="0" cellpadding="0" cellspacing="0" width="100%"> | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td> | ||||||||||||||||||
| <img src="${dataset.imageUrl}" alt="CTA Image" class="kg-cta-image"> | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| </table> | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td class="kg-cta-text"> | ||||||||||||||||||
| ${dataset.textValue} | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| ${dataset.showButton ? ` | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td> | ||||||||||||||||||
| <table border="0" cellpadding="0" cellspacing="0" width="100%"> | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td class="kg-cta-button-container" style="${buttonStyle}"> | ||||||||||||||||||
| <a href="${dataset.buttonUrl}" | ||||||||||||||||||
| class="kg-cta-button ${dataset.buttonColor === 'accent' ? 'kg-style-accent' : ''}" | ||||||||||||||||||
| style="${buttonStyle}"> | ||||||||||||||||||
| ${dataset.buttonText} | ||||||||||||||||||
| </a> | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| </table> | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| </table> | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| `; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| return ` | ||||||||||||||||||
| <div class="cta-card-email" style="${backgroundStyle} padding: 16px; text-align: center; border-radius: 8px;"> | ||||||||||||||||||
| ${dataset.hasImage ? `<img src="${dataset.imageUrl}" alt="CTA Image" style="max-width: 100%; border-radius: 4px;">` : ''} | ||||||||||||||||||
| <div class="cta-text" style="margin-top: 12px; color: ${dataset.textColor};"> | ||||||||||||||||||
| ${dataset.textValue} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ${dataset.showButton ? ` | ||||||||||||||||||
| <a href="${dataset.buttonUrl}" class="cta-button" | ||||||||||||||||||
| style="display: inline-block; margin-top: 12px; padding: 10px 16px; | ||||||||||||||||||
| ${buttonStyle} color: ${dataset.buttonTextColor}; text-decoration: none; | ||||||||||||||||||
| border-radius: 4px;"> | ||||||||||||||||||
| ${dataset.buttonText} | ||||||||||||||||||
| </a> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| <table class="kg-card kg-cta-card kg-cta-bg-${dataset.backgroundColor} kg-cta-${dataset.layout}" border="0" cellpadding="0" cellspacing="0" width="100%"> | ||||||||||||||||||
| ${dataset.hasSponsorLabel ? ` | ||||||||||||||||||
| <div class="sponsor-label" style="margin-top: 8px; font-size: 12px; color: #888;"> | ||||||||||||||||||
| ${dataset.sponsorLabel} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| <tr> | ||||||||||||||||||
| <td class="kg-cta-sponsor-label"> | ||||||||||||||||||
| ${dataset.sponsorLabel} | ||||||||||||||||||
| </td> | ||||||||||||||||||
| </tr> | ||||||||||||||||||
| ` : ''} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ${renderContent()} | ||||||||||||||||||
| </table> | ||||||||||||||||||
| `; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export function renderCallToActionNode(node, options = {}) { | ||||||||||||||||||
| addCreateDocumentOption(options); | ||||||||||||||||||
| const document = options.createDocument(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const dataset = { | ||||||||||||||||||
| layout: node.layout, | ||||||||||||||||||
| textValue: node.textValue, | ||||||||||||||||||
|
|
@@ -72,10 +151,14 @@ export function renderCallToActionNode(node, options = {}) { | |||||||||||||||||
| backgroundColor: node.backgroundColor, | ||||||||||||||||||
| sponsorLabel: node.sponsorLabel, | ||||||||||||||||||
| hasImage: node.hasImage, | ||||||||||||||||||
| imageUrl: node.imageUrl, | ||||||||||||||||||
| textColor: node.textColor | ||||||||||||||||||
| imageUrl: node.imageUrl | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Add validation for backgroundColor | ||||||||||||||||||
| if (!dataset.backgroundColor || !dataset.backgroundColor.match(/^[a-zA-Z\d-]+$/)) { | ||||||||||||||||||
| dataset.backgroundColor = 'white'; | ||||||||||||||||||
| } | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Align backgroundColor validation with buttonColor validation. The validation pattern for backgroundColor is more restrictive than buttonColor. Consider using the same pattern for consistency. -if (!dataset.backgroundColor || !dataset.backgroundColor.match(/^[a-zA-Z\d-]+$/)) {
+if (!dataset.backgroundColor || !dataset.backgroundColor.match(/^[a-zA-Z\d-]+|#([a-fA-F\d]{3}|[a-fA-F\d]{6})$/)) {📝 Committable suggestion
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| if (options.target === 'email') { | ||||||||||||||||||
| const emailDoc = options.createDocument(); | ||||||||||||||||||
| const emailDiv = emailDoc.createElement('div'); | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,8 @@ describe('CallToActionNode', function () { | |
| backgroundColor: 'none', | ||
| hasImage: true, | ||
| imageUrl: 'http://blog.com/image1.jpg', | ||
| imageWidth: 200, | ||
| imageHeight: 100, | ||
| href: '' | ||
| }; | ||
| exportOptions = { | ||
|
|
@@ -69,11 +71,12 @@ describe('CallToActionNode', function () { | |
| callToActionNode.imageUrl.should.equal(dataset.imageUrl); | ||
| callToActionNode.visibility.should.deepEqual(utils.visibility.buildDefaultVisibility()); | ||
| callToActionNode.href.should.equal(dataset.href); | ||
| callToActionNode.imageHeight.should.equal(dataset.imageHeight); | ||
| callToActionNode.imageWidth.should.equal(dataset.imageWidth); | ||
| })); | ||
|
|
||
| it('has setters for all properties', editorTest(function () { | ||
| const callToActionNode = new CallToActionNode(); | ||
|
|
||
| callToActionNode.layout.should.equal('minimal'); | ||
| callToActionNode.layout = 'compact'; | ||
| callToActionNode.layout.should.equal('compact'); | ||
|
|
@@ -122,6 +125,14 @@ describe('CallToActionNode', function () { | |
| callToActionNode.imageUrl = 'http://blog.com/image1.jpg'; | ||
| callToActionNode.imageUrl.should.equal('http://blog.com/image1.jpg'); | ||
|
|
||
| should(callToActionNode.imageHeight).be.null(); | ||
| callToActionNode.imageHeight = 100; | ||
| callToActionNode.imageHeight.should.equal(100); | ||
|
|
||
| should(callToActionNode.imageWidth).be.null(); | ||
| callToActionNode.imageWidth = 200; | ||
| callToActionNode.imageWidth.should.equal(200); | ||
|
|
||
| callToActionNode.href.should.equal(''); | ||
| callToActionNode.href = 'http://blog.com/post1'; | ||
| callToActionNode.href.should.equal('http://blog.com/post1'); | ||
|
|
@@ -208,7 +219,7 @@ describe('CallToActionNode', function () { | |
|
|
||
| const html = element.outerHTML.toString(); | ||
| html.should.containEql('data-layout="minimal"'); | ||
| html.should.containEql('background-color: green'); | ||
| html.should.containEql('kg-cta-bg-green'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add test coverage for color validation. The test suite should include cases for color validation, including:
it('validates buttonColor format', editorTest(function () {
const testCases = [
{input: '#123', expected: '#123'},
{input: '#123456', expected: '#123456'},
{input: 'invalid<script>', expected: 'accent'},
{input: undefined, expected: 'accent'}
];
testCases.forEach(({input, expected}) => {
const node = new CallToActionNode({...dataset, buttonColor: input});
const {element} = node.exportDOM(exportOptions);
if (expected === 'accent') {
element.outerHTML.should.containEql('kg-style-accent');
} else {
element.outerHTML.should.containEql(`background-color: ${expected}`);
}
});
})); |
||
| html.should.containEql('background-color: #F0F0F0'); | ||
| html.should.containEql('Get access now'); | ||
| html.should.containEql('http://someblog.com/somepost'); | ||
|
|
@@ -238,8 +249,7 @@ describe('CallToActionNode', function () { | |
| const {element} = callToActionNode.exportDOM(exportOptions); | ||
|
|
||
| const html = element.outerHTML.toString(); | ||
| html.should.containEql('cta-card-email'); | ||
| html.should.containEql('background-color: green'); | ||
| html.should.containEql('kg-cta-bg-green'); | ||
| html.should.containEql('background-color: #F0F0F0'); | ||
| html.should.containEql('Get access now'); | ||
| html.should.containEql('http://someblog.com/somepost'); | ||
|
|
@@ -311,6 +321,8 @@ describe('CallToActionNode', function () { | |
| hasSponsorLabel: true, | ||
| sponsorLabel: '<p>This post is brought to you by our sponsors</p>', | ||
| imageUrl: '/content/images/2022/11/koenig-lexical.jpg', | ||
| imageWidth: 200, | ||
| imageHeight: 100, | ||
| layout: 'minimal', | ||
| showButton: true, | ||
| textValue: '<p><span style="white-space: pre-wrap;">This is a new CTA Card.</span></p>', | ||
|
|
@@ -331,6 +343,8 @@ describe('CallToActionNode', function () { | |
| hasSponsorLabel: true, | ||
| sponsorLabel: '<p>This post is brought to you by our sponsors</p>', | ||
| imageUrl: '/content/images/2022/11/koenig-lexical.jpg', | ||
| imageWidth: 200, | ||
| imageHeight: 100, | ||
| layout: 'minimal', | ||
| showButton: true, | ||
| textValue: '<p><span style="white-space: pre-wrap;">This is a new CTA Card.</span></p>', | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,7 @@ import {CallToActionCard} from '../components/ui/cards/CallToActionCard.jsx'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {LinkInput} from '../components/ui/LinkInput'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {SnippetActionToolbar} from '../components/ui/SnippetActionToolbar.jsx'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {ToolbarMenu, ToolbarMenuItem, ToolbarMenuSeparator} from '../components/ui/ToolbarMenu.jsx'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {getImageDimensions} from '../utils/getImageDimensions'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {useVisibilityToggle} from '../hooks/useVisibilityToggle.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -91,12 +92,16 @@ export const CallToActionNodeComponent = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleImageChange = async (files) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const imgPreviewUrl = URL.createObjectURL(files[0]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const {width, height} = await getImageDimensions(imgPreviewUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await imageUploader.upload(files); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // reset original src so it can be replaced with preview and upload progress | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| editor.update(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const node = $getNodeByKey(nodeKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.imageUrl = result?.[0].url; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.hasImage = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.imageWidth = width; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.imageHeight = height; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add cleanup for the preview URL to prevent memory leaks. The preview URL created with Apply this diff to add cleanup: const handleImageChange = async (files) => {
const imgPreviewUrl = URL.createObjectURL(files[0]);
- const {width, height} = await getImageDimensions(imgPreviewUrl);
- const result = await imageUploader.upload(files);
- // reset original src so it can be replaced with preview and upload progress
- editor.update(() => {
- const node = $getNodeByKey(nodeKey);
- node.imageUrl = result?.[0].url;
- node.hasImage = true;
- node.imageWidth = width;
- node.imageHeight = height;
- });
+ try {
+ const {width, height} = await getImageDimensions(imgPreviewUrl);
+ const result = await imageUploader.upload(files);
+ // reset original src so it can be replaced with preview and upload progress
+ editor.update(() => {
+ const node = $getNodeByKey(nodeKey);
+ node.imageUrl = result?.[0].url;
+ node.hasImage = true;
+ node.imageWidth = width;
+ node.imageHeight = height;
+ });
+ } finally {
+ URL.revokeObjectURL(imgPreviewUrl);
+ }
};📝 Committable suggestion
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be good to add this in, ObjectURLs will stick around in memory and can use quite a bit when storing large images There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kevinansfield Thanks for confirming! I'll make sure to flag this in future reviews where ObjectURLs are used without proper cleanup. ✏️ Learnings added
If you found this review helpful, would you consider giving us a shout-out on X? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be removed now 😄