@travetto/email-inky 3.4.5 → 4.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/jsx-runtime.ts +17 -8
- package/package.json +6 -9
- package/src/components.ts +6 -1
- package/src/render/context.ts +9 -4
- package/src/render/html.ts +13 -10
- package/src/render/markdown.ts +1 -0
- package/src/render/renderer.ts +5 -6
- package/src/render/subject.ts +1 -0
- package/src/wrapper.ts +15 -24
package/jsx-runtime.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConcreteClass } from '@travetto/base';
|
|
2
|
+
import { EmailTemplateModule, EmailTemplateLocation } from '@travetto/email';
|
|
2
3
|
|
|
3
4
|
export type JSXChild = JSXElement | number | bigint | boolean | object | string;
|
|
4
5
|
type JSXProps = { children?: JSXChild | JSXChild[] | null, className?: string, id?: string, name?: string, dir?: string };
|
|
@@ -53,22 +54,28 @@ declare global {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
let createFrag: Function | undefined = undefined;
|
|
58
|
+
|
|
56
59
|
export function createElement<T extends string | ConcreteClass | JSXComponentFunction<P>, P extends {}>(
|
|
57
60
|
type: T, props: P & JSXProps
|
|
58
61
|
): JSXElement<T, P> {
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
63
|
+
type = (type === createFrag ? JSXFragmentType : type) as T;
|
|
59
64
|
return { [JSXRuntimeTag]: { id: (id += 1) }, type, key: '', props };
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
export function createRootElement<T extends string | ConcreteClass | JSXComponentFunction<P>, P extends {}>(
|
|
63
68
|
type: T, props: P & JSXProps
|
|
64
69
|
): JSXElement<T, P> {
|
|
65
|
-
const res
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const res = createElement(type, props);
|
|
71
|
+
|
|
72
|
+
Object.assign(res, {
|
|
73
|
+
prepare(loc: EmailTemplateLocation): Promise<EmailTemplateModule> {
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
75
|
+
return import('@travetto/email-inky/src/wrapper.js').then(v => v.prepare(res as JSXElement, loc));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
72
79
|
return res;
|
|
73
80
|
}
|
|
74
81
|
|
|
@@ -81,4 +88,6 @@ export const jsxs = createRootElement;
|
|
|
81
88
|
export const Fragment = createFragment;
|
|
82
89
|
export function isJSXElement(el: unknown): el is JSXElement {
|
|
83
90
|
return el !== undefined && el !== null && typeof el === 'object' && JSXRuntimeTag in el;
|
|
84
|
-
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
createFrag = Fragment;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/email-inky",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.1",
|
|
4
4
|
"description": "Email Inky templating module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -27,17 +27,14 @@
|
|
|
27
27
|
"directory": "module/email-inky"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/base": "^
|
|
31
|
-
"@travetto/config": "^
|
|
32
|
-
"@travetto/di": "^
|
|
33
|
-
"@travetto/email": "^
|
|
30
|
+
"@travetto/base": "^4.0.0-rc.1",
|
|
31
|
+
"@travetto/config": "^4.0.0-rc.1",
|
|
32
|
+
"@travetto/di": "^4.0.0-rc.1",
|
|
33
|
+
"@travetto/email": "^4.0.0-rc.1",
|
|
34
34
|
"foundation-emails": "^2.4.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@travetto/email-compiler": "^
|
|
38
|
-
},
|
|
39
|
-
"peerDependencies": {
|
|
40
|
-
"@travetto/cli": "^3.4.7"
|
|
37
|
+
"@travetto/email-compiler": "^4.0.0-rc.1"
|
|
41
38
|
},
|
|
42
39
|
"peerDependenciesMeta": {
|
|
43
40
|
"@travetto/cli": {
|
package/src/components.ts
CHANGED
|
@@ -28,14 +28,19 @@ export const If: CompFn<{ attr: string }> = () => EMPTY;
|
|
|
28
28
|
export const Value: CompFn<{ attr: string, raw?: boolean }> = () => EMPTY;
|
|
29
29
|
export const Unless: CompFn<{ attr: string }> = () => EMPTY;
|
|
30
30
|
export const For: CompFn<{ attr: string }> = () => EMPTY;
|
|
31
|
+
export const InkyTemplate: CompFn<{}> = () => EMPTY;
|
|
31
32
|
|
|
32
33
|
export const c = {
|
|
33
|
-
Wrapper, Container,
|
|
34
|
+
Wrapper, Container, InkyTemplate,
|
|
34
35
|
Column, Title, Summary, HLine, Row, Button,
|
|
35
36
|
BlockGrid, Menu, Item, Center, Callout, Spacer,
|
|
36
37
|
If, Unless, For, Value
|
|
37
38
|
} as const;
|
|
38
39
|
|
|
40
|
+
for (const key of TypedObject.keys(c)) {
|
|
41
|
+
Object.defineProperty(c[key], 'toString', { value: () => `<${key} />` });
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
type C = typeof c;
|
|
40
45
|
|
|
41
46
|
// @ts-expect-error
|
package/src/render/context.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { createElement } from '@travetto/email-inky/jsx-runtime';
|
|
2
|
+
import { FileLoader } from '@travetto/base';
|
|
3
|
+
import { EmailTemplateLocation, EmailResourceLoader } from '@travetto/email';
|
|
2
4
|
|
|
3
5
|
import { JSXElementByFn, c } from '../components';
|
|
4
6
|
|
|
7
|
+
export type RenderContextInit = EmailTemplateLocation & { loader?: FileLoader, columnCount?: number };
|
|
8
|
+
|
|
5
9
|
/**
|
|
6
10
|
* Render Context
|
|
7
11
|
*/
|
|
8
|
-
export class RenderContext {
|
|
12
|
+
export class RenderContext implements RenderContextInit {
|
|
9
13
|
|
|
10
14
|
columnCount: number = 12;
|
|
11
15
|
file: string;
|
|
12
16
|
module: string;
|
|
17
|
+
loader: FileLoader;
|
|
13
18
|
|
|
14
|
-
constructor(
|
|
15
|
-
this
|
|
16
|
-
this.
|
|
19
|
+
constructor(ctx: RenderContextInit) {
|
|
20
|
+
Object.assign(this, ctx);
|
|
21
|
+
this.loader ??= new EmailResourceLoader(ctx.module);
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
/**
|
package/src/render/html.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { JSXElement } from '@travetto/email-inky/jsx-runtime';
|
|
2
|
-
import { EmailResource } from '@travetto/email';
|
|
3
2
|
|
|
4
3
|
import { RenderProvider, RenderState } from '../types';
|
|
5
4
|
import { RenderContext } from './context';
|
|
@@ -37,8 +36,7 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
37
36
|
.replace(/(<[uo]l>)(<li>)/g, (_, a, b) => `${a} ${b}`);
|
|
38
37
|
|
|
39
38
|
if (isRoot) {
|
|
40
|
-
const wrapper = await
|
|
41
|
-
.read('/email/inky.wrapper.html');
|
|
39
|
+
const wrapper = await context.loader.read('/email/inky.wrapper.html');
|
|
42
40
|
|
|
43
41
|
// Get Subject
|
|
44
42
|
const headerTop: string[] = [];
|
|
@@ -76,6 +74,7 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
76
74
|
div: std, span: stdInline, small: stdInline,
|
|
77
75
|
a: async ({ recurse, props }) => `<a ${propsToStr(props)}>${await recurse()}</a>`,
|
|
78
76
|
|
|
77
|
+
InkyTemplate: c => c.recurse(),
|
|
79
78
|
Title: async ({ recurse, el }) => `<title>${await recurse()}</title>`,
|
|
80
79
|
Summary: async ({ recurse, el }) => `<span id="summary" style="${SUMMARY_STYLE}">${await recurse()}</span>`,
|
|
81
80
|
|
|
@@ -89,14 +88,18 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
89
88
|
const sibs = getKids(parent).filter(x => isOfType(x, 'Column'));
|
|
90
89
|
const colCount = sibs.length || 1;
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
pProps.columnVisited
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
if (parent) {
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
93
|
+
const pProps = parent?.props as { columnVisited: boolean };
|
|
94
|
+
if (!pProps.columnVisited) {
|
|
95
|
+
pProps.columnVisited = true;
|
|
96
|
+
if (sibs.length) {
|
|
97
|
+
sibs[0].props.className = classStr(sibs[0].props.className ?? '', 'first');
|
|
98
|
+
sibs[sibs.length - 1].props.className = classStr(sibs[sibs.length - 1].props.className ?? '', 'last');
|
|
99
|
+
}
|
|
99
100
|
}
|
|
101
|
+
} else {
|
|
102
|
+
props.className = classStr(props.className ?? '', 'first', 'last');
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
// Check for sizes. If no attribute is provided, default to small-12. Divide evenly for large columns
|
package/src/render/markdown.ts
CHANGED
|
@@ -46,6 +46,7 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
46
46
|
a: async ({ recurse, props }) => `\n[${await recurse()}](${(props as { href: string }).href})\n`,
|
|
47
47
|
Button: async ({ recurse, props }) => `\n[${await recurse()}](${props.href})\n`,
|
|
48
48
|
|
|
49
|
+
InkyTemplate: visit,
|
|
49
50
|
Callout: visit, Center: visit, Container: visit,
|
|
50
51
|
Column: visit, Wrapper: visit, Row: visit, BlockGrid: visit,
|
|
51
52
|
|
package/src/render/renderer.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { isJSXElement, JSXElement, createFragment, JSXFragmentType, JSXChild } from '@travetto/email-inky/jsx-runtime';
|
|
2
|
-
import { EmailTemplateLocation } from '@travetto/email';
|
|
3
2
|
|
|
4
3
|
import { EMPTY_ELEMENT, getComponentName, JSXElementByFn, c } from '../components';
|
|
5
4
|
import { RenderProvider, RenderState } from '../types';
|
|
6
|
-
import { RenderContext } from './context';
|
|
5
|
+
import { RenderContext, RenderContextInit } from './context';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Inky Renderer
|
|
@@ -34,7 +33,7 @@ export class InkyRenderer {
|
|
|
34
33
|
final = out !== EMPTY_ELEMENT ? out : final;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
if (final.type ===
|
|
36
|
+
if (final.type === JSXFragmentType) {
|
|
38
37
|
return this.#render(ctx, renderer, final.props.children ?? [], stack);
|
|
39
38
|
}
|
|
40
39
|
|
|
@@ -83,11 +82,11 @@ export class InkyRenderer {
|
|
|
83
82
|
static async render(
|
|
84
83
|
root: JSXElement,
|
|
85
84
|
provider: RenderProvider<RenderContext>,
|
|
86
|
-
|
|
85
|
+
context: RenderContextInit,
|
|
87
86
|
isRoot = true
|
|
88
87
|
): Promise<string> {
|
|
89
|
-
const ctx = new RenderContext(
|
|
90
|
-
const par: JSXElement = root.type === JSXFragmentType ? root : {
|
|
88
|
+
const ctx = new RenderContext(context);
|
|
89
|
+
const par: JSXElement = root.type === JSXFragmentType ? root : createFragment({ children: [root] });
|
|
91
90
|
const text = await this.#render(ctx, provider, par, []);
|
|
92
91
|
return provider.finalize(text, ctx, isRoot);
|
|
93
92
|
}
|
package/src/render/subject.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const Subject: RenderProvider<RenderContext> = {
|
|
|
13
13
|
Unless: async ({ recurse, props }) => `{{^${props.attr}}}${await recurse()}{{/${props.attr}}}`,
|
|
14
14
|
Value: async ({ props }) => `{{${props.attr}}}`,
|
|
15
15
|
Title: visit,
|
|
16
|
+
InkyTemplate: visit,
|
|
16
17
|
|
|
17
18
|
title: visit, span: visit, strong: visit, center: visit, em: visit, p: visit, small: visit,
|
|
18
19
|
|
package/src/wrapper.ts
CHANGED
|
@@ -1,35 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import { JSXComponentFunction, JSXElement, JSXFragmentType } from '@travetto/email-inky/jsx-runtime';
|
|
5
|
-
import { RootIndex, path } from '@travetto/manifest';
|
|
1
|
+
import { EmailTemplateModule, EmailTemplateLocation, EmailResourceLoader } from '@travetto/email';
|
|
2
|
+
import { PackageUtil, path } from '@travetto/manifest';
|
|
3
|
+
import { JSXElement } from '@travetto/email-inky/jsx-runtime';
|
|
6
4
|
|
|
7
5
|
import { InkyRenderer } from './render/renderer';
|
|
8
6
|
import { Html } from './render/html';
|
|
9
7
|
import { Markdown } from './render/markdown';
|
|
10
8
|
import { Subject } from './render/subject';
|
|
11
9
|
|
|
12
|
-
export
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
export async function prepare(node: JSXElement, loc: EmailTemplateLocation): Promise<EmailTemplateModule> {
|
|
11
|
+
const ctx = {
|
|
12
|
+
...loc,
|
|
13
|
+
loader: new EmailResourceLoader(loc.module, [path.dirname(PackageUtil.resolveImport('foundation-emails/scss/_global.scss'))])
|
|
14
|
+
};
|
|
16
15
|
return {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
global: `
|
|
16
|
+
loader: ctx.loader,
|
|
17
|
+
globalStyles: `
|
|
20
18
|
@import 'email/inky.variables';
|
|
21
19
|
@import '_global';
|
|
22
20
|
@import 'foundation-emails';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
subject: InkyRenderer.render.bind(InkyRenderer, finalContent, Subject),
|
|
21
|
+
`,
|
|
22
|
+
html: () => InkyRenderer.render(node, Html, ctx),
|
|
23
|
+
text: () => InkyRenderer.render(node, Markdown, ctx),
|
|
24
|
+
subject: () => InkyRenderer.render(node, Subject, ctx),
|
|
28
25
|
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const InkyTemplate: JSXComponentFunction<{}> = (): JSXElement => ({ type: '', key: '', props: {} });
|
|
32
|
-
|
|
33
|
-
export const unwrap = (element: JSXElement): Promise<EmailCompileSource | undefined> =>
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
35
|
-
(element as unknown as { wrap: (el: JSXElement) => Promise<EmailCompileSource> }).wrap(element);
|
|
26
|
+
}
|