Skip to content

Commit

Permalink
Merge pull request #33 from qvantor/feature/variables
Browse files Browse the repository at this point in the history
Feature/variables
  • Loading branch information
qvantor authored Nov 17, 2023
2 parents 0e4d608 + ceeed60 commit 0121ba3
Show file tree
Hide file tree
Showing 42 changed files with 674 additions and 178 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tmp
/out-tsc
*.g.tsx
*.g.ts
.nx
# dependencies
node_modules

Expand Down Expand Up @@ -42,4 +43,4 @@ Thumbs.db

# Generated Docusaurus files
.docusaurus/
.cache-loader/
.cache-loader/
40 changes: 27 additions & 13 deletions apps/backend/prisma/seed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,35 @@ import TAG_ON_COMPONENTS from './seeds/tagOnComponent';
const admins = ADMIN_EMAILS.split(',');
const prisma = new PrismaClient();
const createTags = async (userId: number) => {
await prisma.tag.createMany({
data: TAGS.map((tag) => ({ ...tag, userId })),
});
const idTable: Record<number, number> = {};
for (const { id, name } of TAGS) {
const tag = await prisma.tag.create({ data: { name, userId } });
idTable[id] = tag.id;
}
console.log(`${TAGS.length} tags created`);
return idTable;
};

const createComponents = async (userId: number) => {
await prisma.component.createMany({
data: COMPONENTS.map((component) => ({
...component,
userId,
})),
});
const createComponents = async (
userId: number,
tagIdTable: Record<number, number>
) => {
const idTable: Record<number, number> = {};
for (const { id, ...component } of COMPONENTS) {
const comp = await prisma.component.create({
data: { ...component, userId },
});
idTable[id] = comp.id;
}
console.log(`${COMPONENTS.length} components created`);
await prisma.tagOnComponent.createMany({ data: TAG_ON_COMPONENTS });
for (const relation of TAG_ON_COMPONENTS) {
await prisma.tagOnComponent.create({
data: {
tagId: tagIdTable[relation.tagId],
componentId: idTable[relation.componentId],
},
});
}
console.log(`${TAG_ON_COMPONENTS.length} tagOnComponent created`);
};

Expand All @@ -36,8 +50,8 @@ const main = async () => {
role: 'ADMIN',
},
}));
await createTags(admin.id);
await createComponents(admin.id);
const tagsId = await createTags(admin.id);
await createComponents(admin.id, tagsId);
};

main().then(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { font } from '@waveditors/theme';

export const RowContainer = styled.div`
display: flex;
Expand All @@ -11,3 +12,11 @@ export const SimpleEditorRow = styled.div<{ half?: true }>`
grid-template-columns: ${({ half }) => (half ? '1fr 1fr' : '2fr 3fr')};
align-items: center;
`;

export const InputWithLabel = styled.div``;
export const Label = styled.label`
display: flex;
justify-content: space-between;
align-items: center;
${font({ type: 'paragraph', size: 'small', weight: 'bold' })}
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useCallback } from 'react';
import { ElementLink } from '@waveditors/editor-model';
import { Checkbox } from 'antd';
import { Checkbox, Tooltip } from 'antd';
import styled from 'styled-components';
import { Input } from '@waveditors/ui-kit';
import { InputVariables } from '@waveditors/text-editor';
import { JSONContent } from '@tiptap/core';
import { HiOutlineVariable } from 'react-icons/hi';
import { InputWithLabel, Label } from '../../../common/components';

interface Props {
value: ElementLink | null;
Expand All @@ -15,24 +18,55 @@ const Root = styled.div`
gap: 5px;
`;

const EmptyUrl = {
type: 'oneLiner',
content: [
{
type: 'paragraph',
},
],
};
const mapValue = (value?: string | JSONContent): JSONContent | undefined => {
if (!value) return;
if (typeof value === 'string') {
return {
type: 'oneLiner',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: value }],
},
],
};
}
return value;
};
export const LinkEditor = ({ value, onChange }: Props) => {
const onChangeInternal = useCallback(
<K extends keyof ElementLink>(key: K) =>
(val: ElementLink[K] | undefined) =>
onChange({
newTab: value?.newTab ?? false,
url: value?.url ?? '',
url: value?.url ?? EmptyUrl,
[key]: val,
}),
[value, onChange]
);
return (
<Root>
<Input
value={value?.url}
placeholder='Link url'
onChange={onChangeInternal('url')}
/>
<InputWithLabel>
<Label>
Url:
<Tooltip title='Variables support. Try with `{`'>
<HiOutlineVariable />
</Tooltip>
</Label>
<InputVariables
onChange={onChangeInternal('url')}
content={mapValue(value?.url) ?? EmptyUrl}
editable
/>
</InputWithLabel>
<Checkbox
checked={value?.newTab}
onChange={(e) => onChangeInternal('newTab')(e.target.checked)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { TextEditorStyle } from '@waveditors/text-editor';
import { VersionsProvider } from '../../versions';
import { MailBuilderVersion } from './mail-builder-version';

export const MailBuilder = () => (
<VersionsProvider>
<TextEditorStyle />
<MailBuilderVersion />
</VersionsProvider>
);
8 changes: 6 additions & 2 deletions libs/editor-model/src/builder/services/create-builder-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import {
} from '../../types';
import { elementToStoreConstructor } from '../../elements/elements/elements.creators';
import { commonUndoRedoEffect } from '../../services';
import { usedColorsModule } from '../../common/modules';
import {
usedColorsModule,
variableElementRelationEffect,
} from '../../common/modules';
import { builderContextToSnapshot } from './mappers';

export const createBuilderContext = (
Expand Down Expand Up @@ -51,10 +54,11 @@ export const createBuilderContext = (
// function for wrap any element json to ElementStore
// with all effects required
const toStore = (element: Element) =>
elementToStoreConstructor(element, { variables })
elementToStoreConstructor(element)
.addEffect(commonUndoRedoEffect(undoRedo))
.addEffect(onChange.effect)
.addEffect(usedColors.elementEffect)
.addEffect(variableElementRelationEffect(variables))
.run(element);

const elements = elementsStoreConstructor({ toStore })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`apply variables should change the var name in text content, if name is changed 1`] = `
Object {
"id": "0.47976090123156934",
"link": null,
"name": "text1",
"params": Object {
"content": Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "hello ",
"type": "text",
},
Object {
"attrs": Object {
"id": "0.7830225981794139",
"label": "Hello world",
},
"type": "variable",
},
Object {
"text": " ",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
},
},
"style": Object {},
"type": "text",
}
`;

exports[`apply variables should remove variable, if variable gone 1`] = `
Object {
"id": "0.47976090123156934",
"link": null,
"name": "text1",
"params": Object {
"content": Object {
"content": Array [
Object {
"content": Array [
Object {
"text": "hello ",
"type": "text",
},
Object {
"text": " ",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
},
},
"style": Object {},
"type": "text",
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
isTipTapVariable,
mapTipTapParentsContent,
TipTapVariable,
TipTapVarLabel,
} from '@waveditors/utils';
import { JSONContent } from '@tiptap/core';
import {
ElementCommon,
ElementLinkUrl,
getElementLinkUrl,
isTextElement,
Text,
TextContent,
} from '../../../elements';
import { Variable, Variables } from '../../../variables';

const applyVariableToTipTapVariable = (
variable: Variable,
tipTapVar: TipTapVariable
): TipTapVariable | null => {
if (variable.label === tipTapVar.attrs.label) return null;
return TipTapVarLabel.set(variable.label)(tipTapVar);
};

const applyVariablesToJSONContent = (
variables: Variables,
content: JSONContent
): JSONContent | null => {
let changed = false;
const result = mapTipTapParentsContent(content, (items) =>
items.reduce<JSONContent[]>((sum, item) => {
if (!isTipTapVariable(item)) return [...sum, item];
const variable = variables.find(
(variable) => variable.id === item.attrs.id
);

// no variables mean that var was removed
if (!variable) {
changed = true;
return sum;
}
const newItem = applyVariableToTipTapVariable(variable, item);
if (!newItem) return [...sum, item];

changed = true;
return [...sum, newItem];
}, [])
);
return changed ? result : null;
};

const applyVariablesToCommonElement = (
variables: Variables,
element: ElementCommon
): ElementCommon | null => {
const url = getElementLinkUrl(element);
if (!url || typeof url === 'string') return null;
const newUrl = applyVariablesToJSONContent(variables, url);
if (!newUrl) return null;
return ElementLinkUrl.set(newUrl)(element);
};

const applyVariablesToText = (
variables: Variables,
text: Text
): Text | null => {
const content = applyVariablesToJSONContent(variables, text.params.content);
if (!content) return null;
return TextContent.set(content)(text);
};

const applyVariablesByType = (
variables: Variables,
element: ElementCommon
): ElementCommon | null => {
if (isTextElement(element)) return applyVariablesToText(variables, element);
return null;
};

export const applyVariablesToElement = (
variables: Variables,
element: ElementCommon
) => {
const common = applyVariablesToCommonElement(variables, element);
const result = applyVariablesByType(variables, common ?? element);
return result ?? common;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BasicCase } from '../../../_tests';
import { applyVariablesToElement } from './apply-variables-to-element';

const { Template, TextWithNameVariable, NameVariable } = BasicCase;

const TextWithNameVariableNode = Template.elements[TextWithNameVariable];

describe('apply variables', () => {
it('should do nothing, if variables the same', () => {
expect(
applyVariablesToElement([NameVariable], TextWithNameVariableNode)
).toBeNull();
});
it('should change the var name in text content, if name is changed', () => {
expect(
applyVariablesToElement(
[{ ...NameVariable, label: 'Hello world' }],
TextWithNameVariableNode
)
).toMatchSnapshot();
});
it('should remove variable, if variable gone', () => {
expect(
applyVariablesToElement([], TextWithNameVariableNode)
).toMatchSnapshot();
});
});
Loading

0 comments on commit 0121ba3

Please sign in to comment.