Skip to content

Commit 35a8d48

Browse files
authored
fix(dashboard,api-service): rename maily for loop to repeat (#7663)
1 parent 0dc9684 commit 35a8d48

File tree

11 files changed

+91
-77
lines changed

11 files changed

+91
-77
lines changed

apps/api/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@aws-sdk/client-secrets-manager": "^3.716.0",
3838
"@godaddy/terminus": "^4.12.1",
3939
"@google-cloud/storage": "^6.2.3",
40-
"@maily-to/render": "^0.0.17",
40+
"@maily-to/render": "^0.0.19",
4141
"@nestjs/axios": "3.0.3",
4242
"@nestjs/common": "10.4.1",
4343
"@nestjs/core": "10.4.1",

apps/api/src/app/environments-v1/usecases/output-renderers/email-output-renderer.spec.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -559,13 +559,13 @@ describe('EmailOutputRendererUsecase', () => {
559559
});
560560
});
561561

562-
describe('for block transformation and expansion', () => {
563-
it('should handle for loop block transformation with array of objects', async () => {
562+
describe('repeat block transformation and expansion', () => {
563+
it('should handle repeat loop block transformation with array of objects', async () => {
564564
const mockTipTapNode: MailyJSONContent = {
565565
type: 'doc',
566566
content: [
567567
{
568-
type: 'for',
568+
type: 'repeat',
569569
attrs: {
570570
each: 'payload.comments',
571571
isUpdatingKey: false,
@@ -610,7 +610,7 @@ describe('EmailOutputRendererUsecase', () => {
610610

611611
const renderCommand = {
612612
controlValues: {
613-
subject: 'For Loop Test',
613+
subject: 'Repeat Loop Test',
614614
body: JSON.stringify(mockTipTapNode),
615615
},
616616
fullPayloadForRender: {
@@ -626,12 +626,12 @@ describe('EmailOutputRendererUsecase', () => {
626626
expect(result.body).to.include('This is an author: <!-- -->Jane<!-- -->Post Title');
627627
});
628628

629-
it('should handle for loop block transformation with array of primitives', async () => {
629+
it('should handle repeat loop block transformation with array of primitives', async () => {
630630
const mockTipTapNode: MailyJSONContent = {
631631
type: 'doc',
632632
content: [
633633
{
634-
type: 'for',
634+
type: 'repeat',
635635
attrs: {
636636
each: 'payload.names',
637637
isUpdatingKey: false,
@@ -662,7 +662,7 @@ describe('EmailOutputRendererUsecase', () => {
662662

663663
const renderCommand = {
664664
controlValues: {
665-
subject: 'For Loop Test',
665+
subject: 'Repeat Loop Test',
666666
body: JSON.stringify(mockTipTapNode),
667667
},
668668
fullPayloadForRender: {

apps/api/src/app/environments-v1/usecases/output-renderers/email-output-renderer.usecase.ts

+7-34
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/* eslint-disable no-param-reassign */
22
import { render as mailyRender, JSONContent as MailyJSONContent } from '@maily-to/render';
33
import { Injectable } from '@nestjs/common';
4-
import { Liquid } from 'liquidjs';
54
import { EmailRenderOutput } from '@novu/shared';
65
import { InstrumentUsecase } from '@novu/application-generic';
6+
77
import { FullPayloadForRender, RenderCommand } from './render-command';
88
import { WrapMailyInLiquidUseCase } from './maily-to-liquid/wrap-maily-in-liquid.usecase';
9-
import { MAILY_ITERABLE_MARK, MailyAttrsEnum, MailyContentTypeEnum } from './maily-to-liquid/maily.types';
9+
import { MAILY_ITERABLE_MARK, MailyAttrsEnum } from './maily-to-liquid/maily.types';
1010
import { parseLiquid } from '../../../shared/helpers/liquid';
11+
import { hasShow, isRepeatNode, isVariableNode } from './maily-to-liquid/maily-utils';
1112

1213
export class EmailOutputRendererCommand extends RenderCommand {}
1314

@@ -88,15 +89,15 @@ export class EmailOutputRendererUsecase {
8889
while (queue.length > 0) {
8990
const current = queue.shift()!;
9091

91-
if (this.hasShow(current.node)) {
92+
if (hasShow(current.node)) {
9293
await this.handleShowNode(current.node, variables, current.parent);
9394
}
9495

95-
if (this.isForNode(current.node)) {
96+
if (isRepeatNode(current.node)) {
9697
await this.handleEachNode(current.node, variables, current.parent);
9798
}
9899

99-
if (this.isVariableNode(current.node)) {
100+
if (isVariableNode(current.node)) {
100101
this.processVariableNodeTypes(current.node);
101102
}
102103

@@ -156,23 +157,6 @@ export class EmailOutputRendererUsecase {
156157
node.text = node.attrs?.id || '';
157158
}
158159

159-
private isForNode(
160-
node: MailyJSONContent
161-
): node is MailyJSONContent & { attrs: { [MailyAttrsEnum.EACH_KEY]: string } } {
162-
return !!(
163-
node.type === MailyContentTypeEnum.FOR &&
164-
node.attrs &&
165-
node.attrs[MailyAttrsEnum.EACH_KEY] !== undefined &&
166-
typeof node.attrs[MailyAttrsEnum.EACH_KEY] === 'string'
167-
);
168-
}
169-
170-
private hasShow(
171-
node: MailyJSONContent
172-
): node is MailyJSONContent & { attrs: { [MailyAttrsEnum.SHOW_IF_KEY]: string } } {
173-
return node.attrs?.[MailyAttrsEnum.SHOW_IF_KEY] !== undefined && node.attrs?.[MailyAttrsEnum.SHOW_IF_KEY] !== null;
174-
}
175-
176160
/**
177161
* For 'each' node, multiply the content by the number of items in the iterable array
178162
* and add indexes to the placeholders.
@@ -229,7 +213,7 @@ export class EmailOutputRendererUsecase {
229213
return nodes.map((node) => {
230214
const processedNode = { ...node };
231215

232-
if (this.isVariableNode(processedNode)) {
216+
if (isVariableNode(processedNode)) {
233217
this.processVariableNodeTypes(processedNode);
234218

235219
if (processedNode.text) {
@@ -257,15 +241,4 @@ export class EmailOutputRendererUsecase {
257241
return Boolean(normalized);
258242
}
259243
}
260-
261-
private isVariableNode(
262-
node: MailyJSONContent
263-
): node is MailyJSONContent & { attrs: { [MailyAttrsEnum.ID]: string } } {
264-
return !!(
265-
node.type === MailyContentTypeEnum.VARIABLE &&
266-
node.attrs &&
267-
node.attrs[MailyAttrsEnum.ID] !== undefined &&
268-
typeof node.attrs[MailyAttrsEnum.ID] === 'string'
269-
);
270-
}
271244
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { JSONContent as MailyJSONContent } from '@maily-to/render';
2+
3+
import { MailyAttrsEnum, MailyContentTypeEnum } from './maily.types';
4+
5+
export const isRepeatNode = (
6+
node: MailyJSONContent
7+
): node is MailyJSONContent & { attrs: { [MailyAttrsEnum.EACH_KEY]: string } } => {
8+
return !!(
9+
(node.type === MailyContentTypeEnum.REPEAT || node.type === MailyContentTypeEnum.FOR) &&
10+
node.attrs &&
11+
node.attrs[MailyAttrsEnum.EACH_KEY] !== undefined &&
12+
typeof node.attrs[MailyAttrsEnum.EACH_KEY] === 'string'
13+
);
14+
};
15+
16+
export const isVariableNode = (
17+
node: MailyJSONContent
18+
): node is MailyJSONContent & { attrs: { [MailyAttrsEnum.ID]: string } } => {
19+
return !!(
20+
node.type === MailyContentTypeEnum.VARIABLE &&
21+
node.attrs &&
22+
node.attrs[MailyAttrsEnum.ID] !== undefined &&
23+
typeof node.attrs[MailyAttrsEnum.ID] === 'string'
24+
);
25+
};
26+
27+
export const hasShow = (
28+
node: MailyJSONContent
29+
): node is MailyJSONContent & { attrs: { [MailyAttrsEnum.SHOW_IF_KEY]: string } } => {
30+
return node.attrs?.[MailyAttrsEnum.SHOW_IF_KEY] !== undefined && node.attrs?.[MailyAttrsEnum.SHOW_IF_KEY] !== null;
31+
};
32+
33+
export const hasAttrs = (node: MailyJSONContent): node is MailyJSONContent & { attrs: Record<string, any> } => {
34+
return !!node.attrs;
35+
};
36+
37+
export const hasMarks = (node: MailyJSONContent): node is MailyJSONContent & { marks: Record<string, any>[] } => {
38+
return !!node.marks;
39+
};

apps/api/src/app/environments-v1/usecases/output-renderers/maily-to-liquid/maily.types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
export enum MailyContentTypeEnum {
22
VARIABLE = 'variable',
3+
REPEAT = 'repeat',
4+
/**
5+
* Legacy enum value maintained for backwards compatibility
6+
* @deprecated
7+
*/
38
FOR = 'for',
49
BUTTON = 'button',
510
IMAGE = 'image',

apps/api/src/app/environments-v1/usecases/output-renderers/maily-to-liquid/wrap-maily-in-liquid.usecase.ts

+7-24
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
/* eslint-disable no-param-reassign */
22
import { Injectable } from '@nestjs/common';
33
import { JSONContent as MailyJSONContent } from '@maily-to/render';
4+
45
import { WrapMailyInLiquidCommand } from './wrap-maily-in-liquid.command';
56
import {
67
MailyContentTypeEnum,
78
MailyAttrsEnum,
89
MAILY_ITERABLE_MARK,
910
MAILY_FIRST_CITIZEN_VARIABLE_KEY,
1011
} from './maily.types';
12+
import { hasAttrs, hasMarks, isRepeatNode } from './maily-utils';
1113

1214
/**
13-
* Enriches Maily JSON content with Liquid syntax for variables.
15+
* Enriches Maily JSON content with Liquid syntax repeat variables.
1416
*
1517
* @example
1618
* Input:
1719
* {
18-
* type: "for",
20+
* type: "repeat",
1921
* attrs: { each: "payload.comments" },
2022
* content: [{
2123
* type: "variable",
@@ -53,19 +55,19 @@ export class WrapMailyInLiquidUseCase {
5355
const newNode = { ...node } as MailyJSONContent & { attrs: Record<string, any> };
5456

5557
// if this is a for loop node, track its variable
56-
if (this.isForNode(node)) {
58+
if (isRepeatNode(node)) {
5759
parentForLoopKey = node.attrs[MailyAttrsEnum.EACH_KEY];
5860
}
5961

6062
if (node.content) {
6163
newNode.content = node.content.map((child) => this.wrapVariablesInLiquid(child, parentForLoopKey));
6264
}
6365

64-
if (this.hasAttrs(node)) {
66+
if (hasAttrs(node)) {
6567
newNode.attrs = this.processVariableNodeAttributes(node, parentForLoopKey);
6668
}
6769

68-
if (this.hasMarks(node)) {
70+
if (hasMarks(node)) {
6971
newNode.marks = this.processNodeMarks(node);
7072
}
7173

@@ -144,25 +146,6 @@ export class WrapMailyInLiquidUseCase {
144146

145147
return `{{ ${variableName}${fallbackSuffix} }}`;
146148
}
147-
148-
private hasAttrs(node: MailyJSONContent): node is MailyJSONContent & { attrs: Record<string, any> } {
149-
return !!node.attrs;
150-
}
151-
152-
private hasMarks(node: MailyJSONContent): node is MailyJSONContent & { marks: Record<string, any>[] } {
153-
return !!node.marks;
154-
}
155-
156-
private isForNode(
157-
node: MailyJSONContent
158-
): node is MailyJSONContent & { attrs: { [MailyAttrsEnum.EACH_KEY]: string } } {
159-
return !!(
160-
node.type === MailyContentTypeEnum.FOR &&
161-
node.attrs &&
162-
node.attrs[MailyAttrsEnum.EACH_KEY] !== undefined &&
163-
typeof node.attrs[MailyAttrsEnum.EACH_KEY] === 'string'
164-
);
165-
}
166149
}
167150

168151
const variableAttributeConfig = (type: MailyContentTypeEnum) => {

apps/api/src/app/workflows-v2/maily-test-data.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export function fullCodeSnippet() {
227227
},
228228
content: [
229229
{
230-
type: 'for',
230+
type: 'repeat',
231231
attrs: {
232232
each: 'payload.origins',
233233
isUpdatingKey: false,
@@ -307,7 +307,7 @@ export function fullCodeSnippet() {
307307
},
308308
content: [
309309
{
310-
type: 'for',
310+
type: 'repeat',
311311
attrs: {
312312
each: 'payload.students',
313313
isUpdatingKey: false,
@@ -400,12 +400,12 @@ export function fullCodeSnippet() {
400400
content: [
401401
{
402402
type: 'text',
403-
text: 'This will be a nested for block',
403+
text: 'This will be a nested repeat block',
404404
},
405405
],
406406
},
407407
{
408-
type: 'for',
408+
type: 'repeat',
409409
attrs: {
410410
each: 'payload.food.items',
411411
isUpdatingKey: false,

apps/dashboard/src/components/workflow-editor/steps/email/extensions/for-view.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function TooltipContent({ forNodeEachKey, currentProperty }: { forNodeEachKey: s
77
return (
88
<p className="mly-top-1/2 mly-left-1/2 -mly-translate-x-1/2 -mly-translate-y-1/2 mly-text-gray-400 mly-shadow-sm absolute z-[1] flex items-center gap-2 rounded-md bg-white px-3 py-1.5">
99
<Lightbulb className="size-3.5 stroke-[2] text-gray-400" />
10-
Access 'for' loop items using{' '}
10+
Access 'repeat' loop items using{' '}
1111
<code className="mly-px-1 mly-py-0.5 mly-bg-gray-50 mly-rounded mly-font-mono mly-text-gray-400">
1212
{`{{ ${forNodeEachKey}`}
1313
<span className="inline-block pr-1">
@@ -71,7 +71,7 @@ export function ForView(props: NodeViewProps) {
7171
}, []);
7272

7373
return (
74-
<NodeViewWrapper draggable="true" data-drag-handle="" data-type="for" className="mly-relative">
74+
<NodeViewWrapper draggable="true" data-drag-handle="" data-type="repeat" className="mly-relative">
7575
<NodeViewContent className="is-editable" />
7676
{isOnEmptyForNodeLine && forNodeEachKey && (
7777
<TooltipContent forNodeEachKey={forNodeEachKey} currentProperty={currentProperty} />

apps/dashboard/src/components/workflow-editor/steps/email/extensions/for.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ declare module '@tiptap/core' {
2424
* @see https://github.com/arikchakma/maily.to/blob/d7ea26e6b28201fc66c241200adaebc689018b03/packages/core/src/editor/nodes/for/for.ts
2525
*/
2626
export const ForExtension = Node.create({
27-
name: 'for',
27+
name: 'repeat',
2828
group: 'block',
2929
content: '(block|columns)+',
3030
draggable: true,

apps/dashboard/src/components/workflow-editor/steps/email/maily.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export const Maily = ({ value, onChange, className, ...rest }: MailyProps) => {
8686
return dedupAndSortVariables(filteredVariables, queryWithoutSuffix);
8787
}
8888

89-
const iterableName = editor?.getAttributes('for')?.each;
89+
const iterableName = editor?.getAttributes('repeat')?.each;
9090

9191
const newNamespaces = [...namespaces, ...(iterableName ? [{ name: iterableName, required: false }] : [])];
9292

@@ -123,7 +123,7 @@ export const Maily = ({ value, onChange, className, ...rest }: MailyProps) => {
123123
<>
124124
<div className={cn('mx-auto flex h-full flex-col items-start', className)} {...rest}>
125125
<Editor
126-
key="for-block-enabled"
126+
key="repeat-block-enabled"
127127
config={DEFAULT_EDITOR_CONFIG}
128128
blocks={DEFAULT_EDITOR_BLOCKS}
129129
extensions={extensions}

pnpm-lock.yaml

+16-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)