Feat/email composer improvements (#23188)
- Move composer to dedicated page - Add test email option - Auto saved as draft can be revisited from `objects/messageCampaigns` later - Campaign stats component https://github.com/user-attachments/assets/9e523116-e79b-496d-9c9d-3887e0c9213f <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23188?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
+1
-6
@@ -35,10 +35,6 @@ import { Button, type SelectOption } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const EMAIL_EDITOR_MIN_HEIGHT = 340;
|
||||
|
||||
const EMAIL_EDITOR_MAX_WIDTH = 600;
|
||||
|
||||
type WorkflowEditActionEmailBaseProps = {
|
||||
action: WorkflowEmailAction;
|
||||
actionOptions:
|
||||
@@ -375,8 +371,7 @@ export const WorkflowEditActionEmailBase = ({
|
||||
children: t`Email Editor`,
|
||||
},
|
||||
]}
|
||||
minHeight={EMAIL_EDITOR_MIN_HEIGHT}
|
||||
maxWidth={EMAIL_EDITOR_MAX_WIDTH}
|
||||
preset="workflowEmailBody"
|
||||
/>
|
||||
<WorkflowSendEmailAttachments
|
||||
label={t`Attachments`}
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { getInitialAdvancedTextEditorContent } from '@/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent';
|
||||
|
||||
describe('getInitialAdvancedTextEditorContent', () => {
|
||||
it('should return an empty document when the content is blank', () => {
|
||||
expect(getInitialAdvancedTextEditorContent(' ')).toEqual({
|
||||
type: 'doc',
|
||||
content: [{ type: 'paragraph', content: [] }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the parsed document when the content is TipTap JSON', () => {
|
||||
const document = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] },
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
getInitialAdvancedTextEditorContent(JSON.stringify(document)),
|
||||
).toEqual(document);
|
||||
});
|
||||
|
||||
it('should wrap a BlockNote array in a document', () => {
|
||||
const blocks = [{ type: 'paragraph', content: [] }];
|
||||
|
||||
expect(getInitialAdvancedTextEditorContent(JSON.stringify(blocks))).toEqual(
|
||||
{
|
||||
type: 'doc',
|
||||
content: blocks,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should hand legacy HTML back untouched so TipTap parses it', () => {
|
||||
const html = '<p>Hi <strong>there</strong></p><p>Bye</p>';
|
||||
|
||||
expect(getInitialAdvancedTextEditorContent(html)).toBe(html);
|
||||
});
|
||||
|
||||
it('should hand legacy HTML back untouched when it is indented', () => {
|
||||
const html = '\n <h1 class="title">Title</h1>';
|
||||
|
||||
expect(getInitialAdvancedTextEditorContent(html)).toBe(html);
|
||||
});
|
||||
|
||||
it('should still convert plain text with variables into variable tags', () => {
|
||||
expect(getInitialAdvancedTextEditorContent('Hi {{firstName}}')).toEqual({
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: 'Hi ' },
|
||||
{ type: 'variableTag', attrs: { variable: '{{firstName}}' } },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not treat plain text that merely contains angle brackets as HTML', () => {
|
||||
const text = 'Reply to a <b>bold</b> claim';
|
||||
|
||||
expect(getInitialAdvancedTextEditorContent(text)).toEqual({
|
||||
type: 'doc',
|
||||
content: [{ type: 'paragraph', content: [{ type: 'text', text }] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
+12
-1
@@ -1,12 +1,19 @@
|
||||
import { getInitialEditorContent } from '@/workflow/workflow-variables/utils/getInitialEditorContent';
|
||||
import type { JSONContent } from '@tiptap/react';
|
||||
|
||||
// Campaign bodies were stored as the editor's own HTML before they moved to
|
||||
// JSON. TipTap parses an HTML string natively, so hand it through untouched
|
||||
// rather than letting the plain-text fallback render the markup as literal
|
||||
// text. Requiring a leading tag keeps plain-text bodies, which need the
|
||||
// variable-tag conversion below, out of this branch.
|
||||
const LEADING_HTML_TAG_PATTERN = /^<[a-z][a-z0-9]*(\s[^>]*)?>/i;
|
||||
|
||||
// Previous format of the email body was plain text,
|
||||
// but from now on we will save it as JSON.
|
||||
// So it will fail to parse the content, that's why we have this fallback.
|
||||
export const getInitialAdvancedTextEditorContent = (
|
||||
rawContent: string,
|
||||
): JSONContent => {
|
||||
): JSONContent | string => {
|
||||
// Handle empty or null content
|
||||
if (!rawContent || rawContent.trim() === '') {
|
||||
return {
|
||||
@@ -33,6 +40,10 @@ export const getInitialAdvancedTextEditorContent = (
|
||||
|
||||
return json;
|
||||
} catch {
|
||||
if (LEADING_HTML_TAG_PATTERN.test(rawContent.trim())) {
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
return getInitialEditorContent(rawContent);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user