@travetto/email-inky 7.0.0-rc.1 → 7.0.0-rc.2
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 +7 -7
- package/package.json +6 -6
- package/src/components.ts +1 -1
- package/src/render/common.ts +15 -15
- package/src/render/html.ts +69 -69
- package/src/render/markdown.ts +5 -5
- package/src/render/renderer.ts +15 -15
- package/src/types.ts +2 -2
- package/src/wrapper.ts +3 -3
package/jsx-runtime.ts
CHANGED
|
@@ -41,15 +41,15 @@ export function createElement<T extends string | Class | JSXComponentFunction<P>
|
|
|
41
41
|
export function createRootElement<T extends string | Class | JSXComponentFunction<P>, P extends {}>(
|
|
42
42
|
type: T, props: P & JSXProps
|
|
43
43
|
): JSXElement<T, P> {
|
|
44
|
-
const
|
|
44
|
+
const node = createElement(type, props);
|
|
45
45
|
|
|
46
|
-
Object.assign(
|
|
47
|
-
prepare(
|
|
48
|
-
return import('@travetto/email-inky').then(
|
|
46
|
+
Object.assign(node, {
|
|
47
|
+
prepare(location: EmailTemplateLocation): Promise<EmailTemplateModule> {
|
|
48
|
+
return import('@travetto/email-inky').then(value => value.prepare(castTo(node), location));
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
return
|
|
52
|
+
return node;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export function createFragment<P extends {}>(props: P & JSXProps): JSXElement<typeof JSXFragmentType, P> {
|
|
@@ -59,8 +59,8 @@ export function createFragment<P extends {}>(props: P & JSXProps): JSXElement<ty
|
|
|
59
59
|
export const jsx = createElement;
|
|
60
60
|
export const jsxs = createRootElement;
|
|
61
61
|
export const Fragment = createFragment;
|
|
62
|
-
export function isJSXElement(
|
|
63
|
-
return
|
|
62
|
+
export function isJSXElement(value: unknown): value is JSXElement {
|
|
63
|
+
return value !== undefined && value !== null && typeof value === 'object' && JSXRuntimeTag in value;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
createFrag = Fragment;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/email-inky",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.2",
|
|
4
4
|
"description": "Email Inky templating module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"directory": "module/email-inky"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/config": "^7.0.0-rc.
|
|
31
|
-
"@travetto/di": "^7.0.0-rc.
|
|
32
|
-
"@travetto/email": "^7.0.0-rc.
|
|
33
|
-
"@travetto/runtime": "^7.0.0-rc.
|
|
30
|
+
"@travetto/config": "^7.0.0-rc.2",
|
|
31
|
+
"@travetto/di": "^7.0.0-rc.2",
|
|
32
|
+
"@travetto/email": "^7.0.0-rc.2",
|
|
33
|
+
"@travetto/runtime": "^7.0.0-rc.2",
|
|
34
34
|
"foundation-emails": "^2.4.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@travetto/email-compiler": "^7.0.0-rc.
|
|
37
|
+
"@travetto/email-compiler": "^7.0.0-rc.2"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@travetto/cli": {
|
package/src/components.ts
CHANGED
|
@@ -49,7 +49,7 @@ export type JSXElements = { [K in keyof C]: JSXElementByFn<K>; };
|
|
|
49
49
|
|
|
50
50
|
export const EMPTY_ELEMENT = EMPTY;
|
|
51
51
|
|
|
52
|
-
const invertedC = new Map<Function, string>(TypedObject.entries(c).map(
|
|
52
|
+
const invertedC = new Map<Function, string>(TypedObject.entries(c).map(([name, cls]) => [cls, name] as const));
|
|
53
53
|
|
|
54
54
|
export function getComponentName(fn: Function | string): string {
|
|
55
55
|
if (typeof fn === 'string') {
|
package/src/render/common.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JSXElement, isJSXElement } from '@travetto/email-inky/jsx-runtime';
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
const kids =
|
|
3
|
+
export const getChildren = (node: JSXElement): JSXElement[] => {
|
|
4
|
+
const kids = node?.props?.children;
|
|
5
5
|
let result: unknown[] = [];
|
|
6
6
|
if (kids) {
|
|
7
7
|
result = !Array.isArray(kids) ? [kids] : kids;
|
|
@@ -9,22 +9,22 @@ export const getKids = (el: JSXElement): JSXElement[] => {
|
|
|
9
9
|
return result.filter(isJSXElement);
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export const visit = (
|
|
12
|
+
export const visit = (node: JSXElement, onVisit: (fn: JSXElement) => boolean | undefined | void, depth = 0): boolean | undefined => {
|
|
13
13
|
if (depth > 0) {
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
14
|
+
const result = onVisit(node);
|
|
15
|
+
if (result === true) {
|
|
16
16
|
return true;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
for (const item of
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
19
|
+
for (const item of getChildren(node)) {
|
|
20
|
+
const result = visit(item, onVisit, depth + 1);
|
|
21
|
+
if (result) {
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
export const
|
|
27
|
+
export const classString = (existing: string | undefined, ...toAdd: string[]): string => {
|
|
28
28
|
const out = [];
|
|
29
29
|
const seen = new Set<string>();
|
|
30
30
|
for (const item of existing?.split(/\s+/) ?? []) {
|
|
@@ -42,12 +42,12 @@ export const classStr = (existing: string | undefined, ...toAdd: string[]): stri
|
|
|
42
42
|
return out.join(' ');
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
export const
|
|
46
|
-
const out = { ...props, className:
|
|
45
|
+
export const combinePropsToString = (allowedProps: Set<string>, props: { className?: string } & Record<string, unknown>, addClasses: string[] = []): string => {
|
|
46
|
+
const out = { ...props, className: classString(props.className, ...addClasses) };
|
|
47
47
|
return Object.entries(out)
|
|
48
|
-
.filter(([
|
|
49
|
-
.map(([
|
|
50
|
-
.map(([
|
|
48
|
+
.filter(([key, value]) => allowedProps.has(key) && value !== undefined && value !== null && value !== '')
|
|
49
|
+
.map(([key, value]) => [key === 'className' ? 'class' : key, value])
|
|
50
|
+
.map(([key, value]) => `${key}="${value}"`).join(' ');
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
export const isOfType = (
|
|
53
|
+
export const isOfType = (node: JSXElement, type: string): boolean => typeof node.type === 'function' && node.type.name === type;
|
package/src/render/html.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { JSXElement } from '@travetto/email-inky/jsx-runtime';
|
|
|
2
2
|
|
|
3
3
|
import { RenderProvider, RenderState } from '../types.ts';
|
|
4
4
|
import { RenderContext } from './context.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { classString, combinePropsToString, getChildren, isOfType, visit } from './common.ts';
|
|
6
6
|
|
|
7
7
|
export const SUMMARY_STYLE = Object.entries({
|
|
8
8
|
display: 'none',
|
|
@@ -13,7 +13,7 @@ export const SUMMARY_STYLE = Object.entries({
|
|
|
13
13
|
'max-width': '0px',
|
|
14
14
|
opacity: '0',
|
|
15
15
|
overflow: 'hidden'
|
|
16
|
-
}).map(([
|
|
16
|
+
}).map(([key, value]) => `${key}: ${value}`).join('; ');
|
|
17
17
|
|
|
18
18
|
const allowedProps = new Set([
|
|
19
19
|
'className', 'id', 'dir', 'name', 'src',
|
|
@@ -21,18 +21,18 @@ const allowedProps = new Set([
|
|
|
21
21
|
'width', 'style', 'align', 'valign'
|
|
22
22
|
]);
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const propsToString = combinePropsToString.bind(null, allowedProps);
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
`<${
|
|
26
|
+
const standardInline = async ({ recurse, node }: RenderState<JSXElement, RenderContext>): Promise<string> =>
|
|
27
|
+
`<${node.type} ${propsToString(node.props)}>${await recurse()}</${node.type}>`;
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
const
|
|
29
|
+
const standard = async (state: RenderState<JSXElement, RenderContext>): Promise<string> => `${await standardInline(state)}\n`;
|
|
30
|
+
const standardFull = async (state: RenderState<JSXElement, RenderContext>): Promise<string> => `\n${await standardInline(state)}\n`;
|
|
31
31
|
|
|
32
32
|
export const Html: RenderProvider<RenderContext> = {
|
|
33
33
|
finalize: async (html, context, isRoot = false) => {
|
|
34
34
|
html = html
|
|
35
|
-
.replace(/(<[/](?:a)>)([A-Za-z0-9$])/g, (_, tag,
|
|
35
|
+
.replace(/(<[/](?:a)>)([A-Za-z0-9$])/g, (_, tag, value) => `${tag} ${value}`)
|
|
36
36
|
.replace(/(<[uo]l>)(<li>)/g, (_, a, b) => `${a} ${b}`);
|
|
37
37
|
|
|
38
38
|
if (isRoot) {
|
|
@@ -47,13 +47,13 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
47
47
|
.replace('<!-- BODY -->', html)
|
|
48
48
|
.replace(/<title>.*?<\/title>/, a => { headerTop.push(a); return ''; })
|
|
49
49
|
.replace(/<span[^>]+id="summary"[^>]*>(.*?)<\/span>/sm, a => { bodyTop.push(a); return ''; })
|
|
50
|
-
.replace(/<head( [^>]*)?>/,
|
|
51
|
-
.replace(/<body[^>]*>/,
|
|
50
|
+
.replace(/<head( [^>]*)?>/, tag => `${tag}\n${headerTop.join('\n')}`)
|
|
51
|
+
.replace(/<body[^>]*>/, tag => `${tag}\n${bodyTop.join('\n')}`);
|
|
52
52
|
|
|
53
53
|
// Allow tag suffixes/prefixes via comments
|
|
54
54
|
html = final
|
|
55
|
-
.replace(/\s*<!--\s*[$]:([^ -]+)\s*-->\s*(<\/[^>]+>)/g, (_,
|
|
56
|
-
.replace(/(<[^\/][^>]+>)\s*<!--\s*[#]:([^ ]+)\s*-->\s*/g, (_, tag,
|
|
55
|
+
.replace(/\s*<!--\s*[$]:([^ -]+)\s*-->\s*(<\/[^>]+>)/g, (_, suffix, tag) => `${tag}${suffix}`)
|
|
56
|
+
.replace(/(<[^\/][^>]+>)\s*<!--\s*[#]:([^ ]+)\s*-->\s*/g, (_, tag, prefix) => `${prefix}${tag}`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
return html;
|
|
@@ -65,51 +65,51 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
65
65
|
Value: async ({ props }) => props.raw ? `{{{${props.attr}}}}` : `{{${props.attr}}}`,
|
|
66
66
|
|
|
67
67
|
br: async () => '<br>\n',
|
|
68
|
-
hr: async (
|
|
69
|
-
strong:
|
|
70
|
-
h1:
|
|
71
|
-
li:
|
|
72
|
-
table:
|
|
73
|
-
title:
|
|
74
|
-
div:
|
|
75
|
-
a: async ({ recurse, props }) => `<a ${
|
|
76
|
-
|
|
77
|
-
InkyTemplate:
|
|
68
|
+
hr: async (node) => `<table ${propsToString(node.props)}><th></th></table>`,
|
|
69
|
+
strong: standardInline, em: standardInline, p: standardFull,
|
|
70
|
+
h1: standardFull, h2: standardFull, h3: standardFull, h4: standardFull,
|
|
71
|
+
li: standard, ol: standardFull, ul: standardFull,
|
|
72
|
+
table: standardFull, thead: standard, tr: standard, td: standard, th: standard, tbody: standard, center: standard, img: standardInline,
|
|
73
|
+
title: standard,
|
|
74
|
+
div: standard, span: standardInline, small: standardInline,
|
|
75
|
+
a: async ({ recurse, props }) => `<a ${propsToString(props)}>${await recurse()}</a>`,
|
|
76
|
+
|
|
77
|
+
InkyTemplate: ctx => ctx.recurse(),
|
|
78
78
|
Title: async ({ recurse }) => `<title>${await recurse()}</title>`,
|
|
79
79
|
Summary: async ({ recurse }) => `<span id="summary" style="${SUMMARY_STYLE}">${await recurse()}</span>`,
|
|
80
80
|
|
|
81
|
-
Column: async ({ props, recurse, stack,
|
|
81
|
+
Column: async ({ props, recurse, stack, node, context }): Promise<string> => {
|
|
82
82
|
|
|
83
83
|
recurse();
|
|
84
84
|
|
|
85
85
|
let expander = '';
|
|
86
86
|
|
|
87
87
|
const parent = stack.at(-1)!;
|
|
88
|
-
const
|
|
89
|
-
const colCount =
|
|
88
|
+
const siblings = getChildren(parent).filter(child => isOfType(child, 'Column'));
|
|
89
|
+
const colCount = siblings.length || 1;
|
|
90
90
|
|
|
91
91
|
if (parent) {
|
|
92
|
-
const
|
|
93
|
-
if (!
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
const parentNode: (typeof parent) & { columnVisited?: boolean } = parent;
|
|
93
|
+
if (!parentNode.columnVisited) {
|
|
94
|
+
parentNode.columnVisited = true;
|
|
95
|
+
if (siblings.length) {
|
|
96
|
+
siblings[0].props.className = classString(siblings[0].props.className ?? '', 'first');
|
|
97
|
+
siblings.at(-1)!.props.className = classString(siblings.at(-1)!.props.className ?? '', 'last');
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
} else {
|
|
101
|
-
props.className =
|
|
101
|
+
props.className = classString(props.className ?? '', 'first', 'last');
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Check for sizes. If no attribute is provided, default to small-12. Divide evenly for large columns
|
|
105
|
-
const smallSize =
|
|
106
|
-
const largeSize =
|
|
105
|
+
const smallSize = node.props.small ?? context.columnCount;
|
|
106
|
+
const largeSize = node.props.large ?? node.props.small ?? Math.trunc(context.columnCount / colCount);
|
|
107
107
|
|
|
108
108
|
// If the column contains a nested row, the .expander class should not be used
|
|
109
109
|
if (largeSize === context.columnCount && !props.noExpander) {
|
|
110
110
|
let hasRow = false;
|
|
111
|
-
visit(
|
|
112
|
-
if (isOfType(
|
|
111
|
+
visit(node, (child) => {
|
|
112
|
+
if (isOfType(child, 'Row')) {
|
|
113
113
|
return hasRow = true;
|
|
114
114
|
}
|
|
115
115
|
});
|
|
@@ -134,7 +134,7 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
134
134
|
|
|
135
135
|
// Final HTML output
|
|
136
136
|
return `
|
|
137
|
-
<th ${
|
|
137
|
+
<th ${propsToString(node.props, classes)}>
|
|
138
138
|
<table>
|
|
139
139
|
<tbody>
|
|
140
140
|
<tr>
|
|
@@ -146,14 +146,14 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
146
146
|
},
|
|
147
147
|
|
|
148
148
|
HLine: async ({ props }) => `
|
|
149
|
-
<table ${
|
|
149
|
+
<table ${propsToString(props, ['h-line'])}>
|
|
150
150
|
<tbody>
|
|
151
151
|
<tr><th> </th></tr>
|
|
152
152
|
</tbody>
|
|
153
153
|
</table>`,
|
|
154
154
|
|
|
155
|
-
Row: async ({ recurse,
|
|
156
|
-
<table ${
|
|
155
|
+
Row: async ({ recurse, node }): Promise<string> => `
|
|
156
|
+
<table ${propsToString(node.props, ['row'])}>
|
|
157
157
|
<tbody>
|
|
158
158
|
<tr>${await recurse()}</tr>
|
|
159
159
|
</tbody>
|
|
@@ -170,19 +170,19 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
170
170
|
if (props.expanded) {
|
|
171
171
|
Object.assign(linkProps, { align: 'center', className: 'float-center' });
|
|
172
172
|
}
|
|
173
|
-
inner = `<a ${
|
|
173
|
+
inner = `<a ${propsToString(linkProps)}>${inner}</a>`;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// If the button is expanded, it needs a <center> tag around the content
|
|
177
177
|
if (props.expanded) {
|
|
178
178
|
inner = await Html.Center(createState('Center', { children: [inner] }));
|
|
179
|
-
rest.className =
|
|
179
|
+
rest.className = classString(rest.className ?? '', 'expand');
|
|
180
180
|
expander = '\n<td class="expander"></td>';
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// The .button class is always there, along with any others on the <button> element
|
|
184
184
|
return `
|
|
185
|
-
<table ${
|
|
185
|
+
<table ${propsToString(rest, ['button'])}>
|
|
186
186
|
<tbody>
|
|
187
187
|
<tr>
|
|
188
188
|
<td>
|
|
@@ -203,22 +203,22 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
203
203
|
},
|
|
204
204
|
|
|
205
205
|
Container: async ({ recurse, props }): Promise<string> => `
|
|
206
|
-
<table align="center" ${
|
|
206
|
+
<table align="center" ${propsToString(props, ['container'])}>
|
|
207
207
|
<tbody>
|
|
208
208
|
<tr><td>${await recurse()}</td></tr>
|
|
209
209
|
</tbody>
|
|
210
210
|
</table>`,
|
|
211
211
|
|
|
212
212
|
BlockGrid: async ({ recurse, props }): Promise<string> => `
|
|
213
|
-
<table ${
|
|
213
|
+
<table ${propsToString(props, ['block-grid', props.up ? `up-${props.up}` : ''])}>
|
|
214
214
|
<tbody>
|
|
215
215
|
<tr>${await recurse()}</tr>
|
|
216
216
|
</tbody>
|
|
217
217
|
</table>`,
|
|
218
218
|
|
|
219
|
-
Menu: async ({ recurse,
|
|
219
|
+
Menu: async ({ recurse, node, props }): Promise<string> => {
|
|
220
220
|
let hasItem = false;
|
|
221
|
-
visit(
|
|
221
|
+
visit(node, (child) => {
|
|
222
222
|
if (isOfType(child, 'Item')) {
|
|
223
223
|
return hasItem = true;
|
|
224
224
|
} else if ((child.type === 'td' || child.type === 'th') && child.props.className?.includes('menu-item')) {
|
|
@@ -233,7 +233,7 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
return `
|
|
236
|
-
<table ${
|
|
236
|
+
<table ${propsToString(props, ['menu'])}>
|
|
237
237
|
<tbody>
|
|
238
238
|
<tr>
|
|
239
239
|
<td>
|
|
@@ -253,28 +253,28 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
253
253
|
Item: async ({ recurse, props }): Promise<string> => {
|
|
254
254
|
const { href, target, ...parentAttrs } = props;
|
|
255
255
|
return `
|
|
256
|
-
<th ${
|
|
257
|
-
<a ${
|
|
256
|
+
<th ${propsToString(parentAttrs, ['menu-item'])}>
|
|
257
|
+
<a ${propsToString({ href, target })}>${await recurse()}</a>
|
|
258
258
|
</th>`;
|
|
259
259
|
},
|
|
260
260
|
|
|
261
|
-
Center: async ({ props, recurse,
|
|
262
|
-
for (const
|
|
263
|
-
Object.assign(
|
|
261
|
+
Center: async ({ props, recurse, node }): Promise<string> => {
|
|
262
|
+
for (const child of getChildren(node)) {
|
|
263
|
+
Object.assign(child.props, {
|
|
264
264
|
align: 'center',
|
|
265
|
-
className:
|
|
265
|
+
className: classString(child.props.className, 'float-center')
|
|
266
266
|
});
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
visit(
|
|
269
|
+
visit(node, child => {
|
|
270
270
|
if (isOfType(child, 'Item')) {
|
|
271
|
-
child.props.className =
|
|
271
|
+
child.props.className = classString(child.props.className, 'float-center');
|
|
272
272
|
}
|
|
273
273
|
return;
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
return `
|
|
277
|
-
<center ${
|
|
277
|
+
<center ${propsToString(props)}>
|
|
278
278
|
${await recurse()}
|
|
279
279
|
</center>
|
|
280
280
|
`;
|
|
@@ -286,10 +286,10 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
286
286
|
delete props.className;
|
|
287
287
|
|
|
288
288
|
return `
|
|
289
|
-
<table ${
|
|
289
|
+
<table ${propsToString(props, ['callout'])}>
|
|
290
290
|
<tbody>
|
|
291
291
|
<tr>
|
|
292
|
-
<th ${
|
|
292
|
+
<th ${propsToString(innerProps, ['callout-inner'])}>
|
|
293
293
|
${await recurse()}
|
|
294
294
|
</th>
|
|
295
295
|
<th class="expander"></th>
|
|
@@ -302,7 +302,7 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
302
302
|
const html: string[] = [];
|
|
303
303
|
const buildSpacer = (size: number | string, extraClass: string = ''): string =>
|
|
304
304
|
`
|
|
305
|
-
<table ${
|
|
305
|
+
<table ${propsToString(props, ['spacer', extraClass])}>
|
|
306
306
|
<tbody>
|
|
307
307
|
<tr>
|
|
308
308
|
<td height="${size}px" style="font-size:${size}px;line-height:${size}px;"> </td>
|
|
@@ -311,15 +311,15 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
311
311
|
</table>
|
|
312
312
|
`;
|
|
313
313
|
|
|
314
|
-
const
|
|
315
|
-
const
|
|
314
|
+
const small = props.small ?? undefined;
|
|
315
|
+
const large = props.large ?? undefined;
|
|
316
316
|
|
|
317
|
-
if (
|
|
318
|
-
if (
|
|
319
|
-
html.push(buildSpacer(
|
|
317
|
+
if (small || large) {
|
|
318
|
+
if (small) {
|
|
319
|
+
html.push(buildSpacer(small, 'hide-for-large'));
|
|
320
320
|
}
|
|
321
|
-
if (
|
|
322
|
-
html.push(buildSpacer(
|
|
321
|
+
if (large) {
|
|
322
|
+
html.push(buildSpacer(large, 'show-for-large'));
|
|
323
323
|
}
|
|
324
324
|
} else {
|
|
325
325
|
html.push(buildSpacer(props.size || 16));
|
|
@@ -328,8 +328,8 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
328
328
|
return html.join('\n');
|
|
329
329
|
},
|
|
330
330
|
|
|
331
|
-
Wrapper: async ({ recurse,
|
|
332
|
-
<table align="center" ${
|
|
331
|
+
Wrapper: async ({ recurse, node }) => `
|
|
332
|
+
<table align="center" ${propsToString(node.props, ['wrapper'])}>
|
|
333
333
|
<tbody>
|
|
334
334
|
<tr>
|
|
335
335
|
<td class="wrapper-inner">
|
package/src/render/markdown.ts
CHANGED
|
@@ -10,8 +10,8 @@ const ignore = async (_: RenderState<JSXElement, RenderContext>): Promise<string
|
|
|
10
10
|
export const Markdown: RenderProvider<RenderContext> = {
|
|
11
11
|
finalize: (text) => {
|
|
12
12
|
text = text
|
|
13
|
-
.replace(/(\[[^\]]{1,100}\]\([^)]{1,1000}\))([A-Za-z0-9$]{1,100})/g, (all, link,
|
|
14
|
-
.replace(/(\S)\n(#)/g, (_,
|
|
13
|
+
.replace(/(\[[^\]]{1,100}\]\([^)]{1,1000}\))([A-Za-z0-9$]{1,100})/g, (all, link, value) => value === 's' ? all : `${link} ${value}`)
|
|
14
|
+
.replace(/(\S)\n(#)/g, (_, left, right) => `${left}\n\n${right}`);
|
|
15
15
|
return text;
|
|
16
16
|
},
|
|
17
17
|
|
|
@@ -29,8 +29,8 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
29
29
|
ul: async ({ recurse }) => `\n${await recurse()}`,
|
|
30
30
|
ol: async ({ recurse }) => `\n${await recurse()}`,
|
|
31
31
|
li: async ({ recurse, stack }) => {
|
|
32
|
-
const parent = stack.toReversed().find(
|
|
33
|
-
const depth = stack.filter(
|
|
32
|
+
const parent = stack.toReversed().find(node => node.type === 'ol' || node.type === 'ul');
|
|
33
|
+
const depth = stack.filter(node => node.type === 'ol' || node.type === 'ul').length;
|
|
34
34
|
return `${' '.repeat(depth)}${(parent && parent.type === 'ol') ? '1.' : '* '} ${await recurse()}\n`;
|
|
35
35
|
},
|
|
36
36
|
th: async ({ recurse }) => `|${await recurse()}`,
|
|
@@ -53,7 +53,7 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
53
53
|
|
|
54
54
|
Menu: async ({ recurse }) => `\n${await recurse()}`,
|
|
55
55
|
Item: async ({ recurse, stack, props }) => {
|
|
56
|
-
const depth = stack.filter(
|
|
56
|
+
const depth = stack.filter(node => node.type === 'Menu').length;
|
|
57
57
|
return `${' '.repeat(depth)}* [${await recurse()}](${props.href})\n`;
|
|
58
58
|
},
|
|
59
59
|
Spacer: async () => '\n\n',
|
package/src/render/renderer.ts
CHANGED
|
@@ -13,23 +13,23 @@ export class InkyRenderer {
|
|
|
13
13
|
static async #render(
|
|
14
14
|
ctx: RenderContext,
|
|
15
15
|
renderer: RenderProvider<RenderContext>,
|
|
16
|
-
|
|
16
|
+
input: JSXChild[] | JSXChild | null | undefined,
|
|
17
17
|
stack: JSXElement[] = []
|
|
18
18
|
): Promise<string> {
|
|
19
|
-
if (
|
|
19
|
+
if (input === null || input === undefined) {
|
|
20
20
|
return '';
|
|
21
|
-
} else if (Array.isArray(
|
|
21
|
+
} else if (Array.isArray(input)) {
|
|
22
22
|
const out: string[] = [];
|
|
23
|
-
const nextStack = [...stack, { key: '', props: { children:
|
|
24
|
-
for (const
|
|
25
|
-
out.push(await this.#render(ctx, renderer,
|
|
23
|
+
const nextStack = [...stack, { key: '', props: { children: input }, type: JSXFragmentType }];
|
|
24
|
+
for (const node of input) {
|
|
25
|
+
out.push(await this.#render(ctx, renderer, node, nextStack));
|
|
26
26
|
}
|
|
27
27
|
return out.join('');
|
|
28
|
-
} else if (isJSXElement(
|
|
29
|
-
let final: JSXElement =
|
|
28
|
+
} else if (isJSXElement(input)) {
|
|
29
|
+
let final: JSXElement = input;
|
|
30
30
|
// Render simple element if needed
|
|
31
|
-
if (typeof
|
|
32
|
-
const out = castTo<Function>(
|
|
31
|
+
if (typeof input.type === 'function' && input.type !== JSXFragmentType) {
|
|
32
|
+
const out = castTo<Function>(input.type)(input.props);
|
|
33
33
|
final = out !== EMPTY_ELEMENT ? out : final;
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -46,7 +46,7 @@ export class InkyRenderer {
|
|
|
46
46
|
const recurse = (): Promise<string> => this.#render(ctx, renderer, final.props.children ?? [], [...stack, final]);
|
|
47
47
|
// @ts-expect-error
|
|
48
48
|
const state: RenderState<JSXElement, RenderContext> = {
|
|
49
|
-
|
|
49
|
+
node: final, props: final.props, recurse, stack, context: ctx
|
|
50
50
|
};
|
|
51
51
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
52
52
|
state.createState = (key, props) => this.createState(ctx, renderer, state, key, props);
|
|
@@ -57,7 +57,7 @@ export class InkyRenderer {
|
|
|
57
57
|
throw new Error(`Unknown element: ${final.type}`);
|
|
58
58
|
}
|
|
59
59
|
} else {
|
|
60
|
-
return `${
|
|
60
|
+
return `${input}`;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -69,9 +69,9 @@ export class InkyRenderer {
|
|
|
69
69
|
props: JSXElementByFn<K>['props'],
|
|
70
70
|
// @ts-expect-error
|
|
71
71
|
): RenderState<JSXElementByFn<K>, RenderContext> {
|
|
72
|
-
const
|
|
73
|
-
const newStack: JSXElement[] = castTo([...state.stack,
|
|
74
|
-
return { ...state,
|
|
72
|
+
const node = ctx.createElement(key, props);
|
|
73
|
+
const newStack: JSXElement[] = castTo([...state.stack, node]);
|
|
74
|
+
return { ...state, node, props: node.props, recurse: () => this.#render(ctx, renderer, node.props.children ?? [], newStack) };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { JSXElement, ValidHtmlTags } from '../jsx-runtime.ts';
|
|
2
2
|
import { JSXElementByFn, c } from './components.ts';
|
|
3
3
|
|
|
4
|
-
export type Wrapper = Record<string, (
|
|
4
|
+
export type Wrapper = Record<string, (count: string) => string>;
|
|
5
5
|
|
|
6
6
|
export type RenderState<T extends JSXElement, C> = {
|
|
7
|
-
|
|
7
|
+
node: T;
|
|
8
8
|
props: T['props'];
|
|
9
9
|
recurse: () => Promise<string>;
|
|
10
10
|
stack: JSXElement[];
|
package/src/wrapper.ts
CHANGED
|
@@ -9,10 +9,10 @@ import { Html } from './render/html.ts';
|
|
|
9
9
|
import { Markdown } from './render/markdown.ts';
|
|
10
10
|
import { Subject } from './render/subject.ts';
|
|
11
11
|
|
|
12
|
-
export async function prepare(node: JSXElement,
|
|
12
|
+
export async function prepare(node: JSXElement, location: EmailTemplateLocation): Promise<EmailTemplateModule> {
|
|
13
13
|
const ctx = {
|
|
14
|
-
...
|
|
15
|
-
loader: new EmailResourceLoader(
|
|
14
|
+
...location,
|
|
15
|
+
loader: new EmailResourceLoader(location.module, [path.dirname(PackageUtil.resolveImport('foundation-emails/scss/_global.scss'))])
|
|
16
16
|
};
|
|
17
17
|
return {
|
|
18
18
|
loader: ctx.loader,
|