email-editor-core 0.0.4
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/README.md +438 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +260 -0
- package/dist/renderer/blocks/button.d.ts +18 -0
- package/dist/renderer/blocks/button.d.ts.map +1 -0
- package/dist/renderer/blocks/button.js +57 -0
- package/dist/renderer/blocks/divider.d.ts +18 -0
- package/dist/renderer/blocks/divider.d.ts.map +1 -0
- package/dist/renderer/blocks/divider.js +42 -0
- package/dist/renderer/blocks/highlight.d.ts +18 -0
- package/dist/renderer/blocks/highlight.d.ts.map +1 -0
- package/dist/renderer/blocks/highlight.js +49 -0
- package/dist/renderer/blocks/image.d.ts +18 -0
- package/dist/renderer/blocks/image.d.ts.map +1 -0
- package/dist/renderer/blocks/image.js +59 -0
- package/dist/renderer/blocks/paragraph.d.ts +18 -0
- package/dist/renderer/blocks/paragraph.d.ts.map +1 -0
- package/dist/renderer/blocks/paragraph.js +41 -0
- package/dist/renderer/blocks/title.d.ts +18 -0
- package/dist/renderer/blocks/title.d.ts.map +1 -0
- package/dist/renderer/blocks/title.js +49 -0
- package/dist/renderer/parseInlineFormatting.d.ts +14 -0
- package/dist/renderer/parseInlineFormatting.d.ts.map +1 -0
- package/dist/renderer/parseInlineFormatting.js +178 -0
- package/dist/renderer/renderBlock.d.ts +21 -0
- package/dist/renderer/renderBlock.d.ts.map +1 -0
- package/dist/renderer/renderBlock.js +44 -0
- package/dist/renderer/renderEmail.d.ts +26 -0
- package/dist/renderer/renderEmail.d.ts.map +1 -0
- package/dist/renderer/renderEmail.js +275 -0
- package/dist/sanitizer.d.ts +147 -0
- package/dist/sanitizer.d.ts.map +1 -0
- package/dist/sanitizer.js +533 -0
- package/dist/template-config.d.ts +38 -0
- package/dist/template-config.d.ts.map +1 -0
- package/dist/template-config.js +196 -0
- package/dist/test-formatting.d.ts +6 -0
- package/dist/test-formatting.d.ts.map +1 -0
- package/dist/test-formatting.js +132 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/validator.d.ts +86 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +435 -0
- package/package.json +17 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMAIL GENERATOR CORE - MAIN EXPORT
|
|
3
|
+
*
|
|
4
|
+
* Block-based HTML email generator with strict validation and sanitization.
|
|
5
|
+
* Rock-solid foundation for marketing email systems.
|
|
6
|
+
*/
|
|
7
|
+
import { OPEN_FUND_CONFIG, CLOSE_FUND_CONFIG, NEWSLETTER_CONFIG, TEMPLATE_CONFIG_REGISTRY, getTemplateConfig, BLOCK_CONSTRAINT_MESSAGES, } from "./template-config.js";
|
|
8
|
+
import { blockTypeAllowedRule, blockCountConstraintRule, totalBlockCountRule, mandatoryBlocksRule, blockIdUniquenessRule, blockContentValidationRule, colorFormatRule, blockOrderRule, fixedSectionsRule, validateEmailDocument, isEmailDocumentValid, getValidationSummary, } from "./validator.js";
|
|
9
|
+
import { GLOBAL_SANITIZATION_CONFIG, BLOCK_SANITIZATION_CONFIG, escapeHtml, isValidUrlProtocol, stripAllHtml, sanitizeAttributes, sanitizeHtml, sanitizeTextContent, isValidUrl, sanitizeButtonLabel, sanitizeImageAlt, sanitizeBlock, getSanitizationReport, } from "./sanitizer.js";
|
|
10
|
+
import { renderEmail } from "./renderer/renderEmail.js";
|
|
11
|
+
import { renderBlock } from "./renderer/renderBlock.js";
|
|
12
|
+
export { OPEN_FUND_CONFIG, CLOSE_FUND_CONFIG, NEWSLETTER_CONFIG, TEMPLATE_CONFIG_REGISTRY, getTemplateConfig, BLOCK_CONSTRAINT_MESSAGES, blockTypeAllowedRule, blockCountConstraintRule, totalBlockCountRule, mandatoryBlocksRule, blockIdUniquenessRule, blockContentValidationRule, colorFormatRule, blockOrderRule, fixedSectionsRule, validateEmailDocument, isEmailDocumentValid, getValidationSummary, GLOBAL_SANITIZATION_CONFIG, BLOCK_SANITIZATION_CONFIG, escapeHtml, isValidUrlProtocol, stripAllHtml, sanitizeAttributes, sanitizeHtml, sanitizeTextContent, isValidUrl, sanitizeButtonLabel, sanitizeImageAlt, sanitizeBlock, getSanitizationReport, renderEmail, renderBlock, };
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// CONVENIENCE FUNCTIONS
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Create a new email document with default structure
|
|
18
|
+
*
|
|
19
|
+
* USAGE:
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const email = createEmail('open-fund', [
|
|
22
|
+
* { type: 'title', id: 'title-1', content: 'Welcome' },
|
|
23
|
+
* { type: 'paragraph', id: 'para-1', content: 'This is an email' }
|
|
24
|
+
* ]);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function createEmail(templateType, blocks, personalizationVars) {
|
|
28
|
+
const config = getTemplateConfig(templateType);
|
|
29
|
+
// Sanitize all blocks
|
|
30
|
+
const sanitizedBlocks = blocks.map((block) => {
|
|
31
|
+
try {
|
|
32
|
+
return sanitizeBlock(block);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
console.error(`Failed to sanitize block ${block.id}:`, e);
|
|
36
|
+
return block;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// Create email document
|
|
40
|
+
const email = {
|
|
41
|
+
id: generateUUID(),
|
|
42
|
+
templateType: templateType,
|
|
43
|
+
version: 1,
|
|
44
|
+
createdAt: new Date(),
|
|
45
|
+
updatedAt: new Date(),
|
|
46
|
+
blocks: sanitizedBlocks,
|
|
47
|
+
body: {
|
|
48
|
+
blocks: sanitizedBlocks,
|
|
49
|
+
},
|
|
50
|
+
header: {
|
|
51
|
+
logoUrl: "https://nobi.id/icons/logo-nobi-dana-kripto-black.png",
|
|
52
|
+
logoHeight: 32,
|
|
53
|
+
},
|
|
54
|
+
helpSection: {
|
|
55
|
+
title: "Butuh Bantuan untuk Mulai?",
|
|
56
|
+
description: "Tim kami siap bantu kamu! Kalau ada pertanyaan atau kendala saat registrasi dan verifikasi, langsung aja hubungi kami via:",
|
|
57
|
+
contactItems: [
|
|
58
|
+
{
|
|
59
|
+
type: "email",
|
|
60
|
+
label: "Email",
|
|
61
|
+
value: "halo@nobi.id",
|
|
62
|
+
href: "mailto:halo@nobi.id",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "whatsapp",
|
|
66
|
+
label: "WhatsApp",
|
|
67
|
+
value: "+62 811-8826-624",
|
|
68
|
+
href: "https://wa.me/628118826624",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
imageUrl: "https://nobi.id/images/hubungi-kami.png",
|
|
72
|
+
},
|
|
73
|
+
complianceSection: {
|
|
74
|
+
text: "PT Dana Kripto Indonesia sebagai peserta sandbox OJK",
|
|
75
|
+
sandboxNumber: "S-196/IK.01/2025",
|
|
76
|
+
},
|
|
77
|
+
footer: {
|
|
78
|
+
logoUrl: "https://nobi.id/icons/logo-nobi-dana-kripto.png",
|
|
79
|
+
companyName: "PT. Dana Kripto Indonesia",
|
|
80
|
+
address: "The Plaza Office Tower - 7th Floor, Jakarta, Indonesia",
|
|
81
|
+
socialLinks: [
|
|
82
|
+
{
|
|
83
|
+
platform: "email",
|
|
84
|
+
url: "mailto:halo@nobi.id",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
platform: "instagram",
|
|
88
|
+
url: "https://www.instagram.com/nobidanakripto/",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
platform: "whatsapp",
|
|
92
|
+
url: "https://wa.me/628118826624?text=Halo%20tim%20NOBI%2C%20saya%20butuh%20bantuan%20mengenai...%20Mohon%20dibantu%20ya.%20Terima%20kasih.",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
personalizationVariables: personalizationVars,
|
|
97
|
+
isValid: false,
|
|
98
|
+
validationErrors: [],
|
|
99
|
+
};
|
|
100
|
+
// Validate
|
|
101
|
+
const summary = getValidationSummary(email, false);
|
|
102
|
+
email.isValid = summary.isValid;
|
|
103
|
+
email.validationErrors = summary.errors;
|
|
104
|
+
return email;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate a comprehensive validation report for an email
|
|
108
|
+
*/
|
|
109
|
+
export function generateReport(email) {
|
|
110
|
+
const summary = getValidationSummary(email, true);
|
|
111
|
+
let report = `
|
|
112
|
+
╔════════════════════════════════════════════════════════╗
|
|
113
|
+
║ EMAIL VALIDATION REPORT ║
|
|
114
|
+
╚════════════════════════════════════════════════════════╝
|
|
115
|
+
|
|
116
|
+
Template Type: ${email.templateType}
|
|
117
|
+
Status: ${summary.isValid ? "✅ VALID" : "❌ INVALID"}
|
|
118
|
+
Blocks: ${email.body.blocks.length}
|
|
119
|
+
Errors: ${summary.errorCount}
|
|
120
|
+
Warnings: ${summary.warningCount}
|
|
121
|
+
|
|
122
|
+
`;
|
|
123
|
+
if (summary.errorCount > 0) {
|
|
124
|
+
report += `ERRORS (${summary.errorCount}):\n`;
|
|
125
|
+
report += "─".repeat(56) + "\n";
|
|
126
|
+
summary.errors.forEach((error) => {
|
|
127
|
+
report += ` [${error.code}]\n`;
|
|
128
|
+
report += ` ${error.message}\n`;
|
|
129
|
+
if (error.blockId)
|
|
130
|
+
report += ` Block: ${error.blockId}\n`;
|
|
131
|
+
report += "\n";
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (summary.warningCount > 0) {
|
|
135
|
+
report += `\nWARNINGS (${summary.warningCount}):\n`;
|
|
136
|
+
report += "─".repeat(56) + "\n";
|
|
137
|
+
summary.warnings.forEach((warning) => {
|
|
138
|
+
report += ` [${warning.code}]\n`;
|
|
139
|
+
report += ` ${warning.message}\n`;
|
|
140
|
+
report += "\n";
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (summary.isValid) {
|
|
144
|
+
report += "\n✅ All validation checks passed!\n";
|
|
145
|
+
}
|
|
146
|
+
report += "\n╔════════════════════════════════════════════════════════╗\n";
|
|
147
|
+
return report;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Generate UUID v4
|
|
151
|
+
*/
|
|
152
|
+
function generateUUID() {
|
|
153
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
154
|
+
const r = (Math.random() * 16) | 0;
|
|
155
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
156
|
+
return v.toString(16);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// DOCUMENTATION
|
|
161
|
+
// ============================================================================
|
|
162
|
+
/**
|
|
163
|
+
* Documentation files included in this module:
|
|
164
|
+
*
|
|
165
|
+
* 📄 ARCHITECTURE.md
|
|
166
|
+
* Complete system design, decisions, security considerations
|
|
167
|
+
*
|
|
168
|
+
* 📄 PSEUDOCODE.md
|
|
169
|
+
* Detailed pseudocode for all major functions
|
|
170
|
+
*
|
|
171
|
+
* 📄 types.ts
|
|
172
|
+
* TypeScript interfaces and types
|
|
173
|
+
*
|
|
174
|
+
* 📄 template-config.ts
|
|
175
|
+
* Per-template rules and constraints
|
|
176
|
+
*
|
|
177
|
+
* 📄 validator.ts
|
|
178
|
+
* 9 validation rules and validation engine
|
|
179
|
+
*
|
|
180
|
+
* 📄 sanitizer.ts
|
|
181
|
+
* Text sanitization and HTML cleaning
|
|
182
|
+
*/
|
|
183
|
+
export const DOCUMENTATION = {
|
|
184
|
+
ARCHITECTURE: "ARCHITECTURE.md",
|
|
185
|
+
PSEUDOCODE: "PSEUDOCODE.md",
|
|
186
|
+
TYPES: "types.ts",
|
|
187
|
+
TEMPLATE_CONFIG: "template-config.ts",
|
|
188
|
+
VALIDATOR: "validator.ts",
|
|
189
|
+
SANITIZER: "sanitizer.ts",
|
|
190
|
+
};
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// QUICK START GUIDE
|
|
193
|
+
// ============================================================================
|
|
194
|
+
/**
|
|
195
|
+
* QUICK START
|
|
196
|
+
*
|
|
197
|
+
* 1. Create an email:
|
|
198
|
+
* ```
|
|
199
|
+
* const email = createEmail('open-fund', [
|
|
200
|
+
* { type: 'title', id: 'h1', content: 'Welcome' },
|
|
201
|
+
* { type: 'paragraph', id: 'p1', content: 'Hello!' }
|
|
202
|
+
* ]);
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* 2. Validate:
|
|
206
|
+
* ```
|
|
207
|
+
* const isValid = isEmailDocumentValid(email);
|
|
208
|
+
* const summary = getValidationSummary(email);
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* 3. Get report:
|
|
212
|
+
* ```
|
|
213
|
+
* console.log(generateReport(email));
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* 4. Sanitize text:
|
|
217
|
+
* ```
|
|
218
|
+
* const clean = sanitizeTextContent(userInput, 'paragraph');
|
|
219
|
+
* ```
|
|
220
|
+
*
|
|
221
|
+
* TEMPLATE TYPES:
|
|
222
|
+
* - 'open-fund' : Fund launch announcement
|
|
223
|
+
* - 'close-fund' : Fund closure notification
|
|
224
|
+
* - 'newsletter' : Educational/updates
|
|
225
|
+
*
|
|
226
|
+
* BLOCK TYPES (all templates):
|
|
227
|
+
* - title : Heading (h1/h2/h3)
|
|
228
|
+
* - paragraph : Body text with links
|
|
229
|
+
* - image : Responsive image (HTTPS)
|
|
230
|
+
* - button : Call-to-action
|
|
231
|
+
* - divider : Visual separator
|
|
232
|
+
* - highlight-box : Callout/feature box
|
|
233
|
+
*
|
|
234
|
+
* VALIDATION:
|
|
235
|
+
* Automatic checks:
|
|
236
|
+
* ✓ Block type allowed per template
|
|
237
|
+
* ✓ Min/max block counts per type
|
|
238
|
+
* ✓ Total block limit
|
|
239
|
+
* ✓ Mandatory blocks present
|
|
240
|
+
* ✓ Unique block IDs
|
|
241
|
+
* ✓ Content not empty
|
|
242
|
+
* ✓ Valid colors (hex format)
|
|
243
|
+
* ✓ Valid URLs (HTTPS for images/buttons)
|
|
244
|
+
* ✓ Required sections present
|
|
245
|
+
*
|
|
246
|
+
* SANITIZATION:
|
|
247
|
+
* Automatic cleaning:
|
|
248
|
+
* ✓ Strip dangerous HTML tags
|
|
249
|
+
* ✓ Remove event handlers
|
|
250
|
+
* ✓ Validate and escape URLs
|
|
251
|
+
* ✓ Escape HTML special chars
|
|
252
|
+
* ✓ Remove inline styles
|
|
253
|
+
* ✓ Block non-HTTPS resources
|
|
254
|
+
*
|
|
255
|
+
* SECURITY:
|
|
256
|
+
* ✓ XSS prevention (character escaping)
|
|
257
|
+
* ✓ URL injection prevention
|
|
258
|
+
* ✓ Email client compatibility
|
|
259
|
+
* ✓ Compliance section protection
|
|
260
|
+
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BUTTON BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single button block to HTML
|
|
5
|
+
* - Receives: ButtonBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for button blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
import type { ButtonBlock } from "../../types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Render a button block to HTML
|
|
13
|
+
*
|
|
14
|
+
* @param block - The button block to render
|
|
15
|
+
* @returns HTML string for this block
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderButtonBlock(block: ButtonBlock): string;
|
|
18
|
+
//# sourceMappingURL=button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/renderer/blocks/button.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CA6C5D"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BUTTON BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single button block to HTML
|
|
5
|
+
* - Receives: ButtonBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for button blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Render a button block to HTML
|
|
12
|
+
*
|
|
13
|
+
* @param block - The button block to render
|
|
14
|
+
* @returns HTML string for this block
|
|
15
|
+
*/
|
|
16
|
+
export function renderButtonBlock(block) {
|
|
17
|
+
// Default values if not specified
|
|
18
|
+
const backgroundColor = block.backgroundColor || "#006950";
|
|
19
|
+
const textColor = block.textColor || "#ffffff";
|
|
20
|
+
const padding = block.padding || "10px 16px";
|
|
21
|
+
const borderRadius = block.borderRadius || 6;
|
|
22
|
+
const marginTop = block.marginTop || 12;
|
|
23
|
+
const paddingBottom = block.paddingBottom || 0;
|
|
24
|
+
const align = block.align || "left";
|
|
25
|
+
// Parse padding if string format like "12px 24px"
|
|
26
|
+
const paddingValue = padding;
|
|
27
|
+
// Inline styles for button link
|
|
28
|
+
const linkStyles = [
|
|
29
|
+
`background-color: ${backgroundColor}`,
|
|
30
|
+
`color: ${textColor}`,
|
|
31
|
+
`padding: ${paddingValue}`,
|
|
32
|
+
"text-decoration: none",
|
|
33
|
+
`border-radius: ${borderRadius}px`,
|
|
34
|
+
"display: inline-block",
|
|
35
|
+
"font-weight: bold",
|
|
36
|
+
"border: none",
|
|
37
|
+
"cursor: pointer",
|
|
38
|
+
].join(";");
|
|
39
|
+
// Container styles
|
|
40
|
+
const containerStyles = [
|
|
41
|
+
`margin-top: ${marginTop}px`,
|
|
42
|
+
`padding-bottom: ${paddingBottom}px`,
|
|
43
|
+
`text-align: ${align}`,
|
|
44
|
+
].join(";");
|
|
45
|
+
// HTML table wrapper (email compatibility)
|
|
46
|
+
return `
|
|
47
|
+
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
48
|
+
<tr>
|
|
49
|
+
<td style="${containerStyles}">
|
|
50
|
+
<a href="${block.href}" style="${linkStyles}">
|
|
51
|
+
${block.label}
|
|
52
|
+
</a>
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</table>
|
|
56
|
+
`.trim();
|
|
57
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DIVIDER BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single divider block to HTML
|
|
5
|
+
* - Receives: DividerBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for divider blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
import type { DividerBlock } from '../../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Render a divider block to HTML
|
|
13
|
+
*
|
|
14
|
+
* @param block - The divider block to render
|
|
15
|
+
* @returns HTML string for this block
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderDividerBlock(block: DividerBlock): string;
|
|
18
|
+
//# sourceMappingURL=divider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"divider.d.ts","sourceRoot":"","sources":["../../../src/renderer/blocks/divider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CA4B9D"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DIVIDER BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single divider block to HTML
|
|
5
|
+
* - Receives: DividerBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for divider blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Render a divider block to HTML
|
|
12
|
+
*
|
|
13
|
+
* @param block - The divider block to render
|
|
14
|
+
* @returns HTML string for this block
|
|
15
|
+
*/
|
|
16
|
+
export function renderDividerBlock(block) {
|
|
17
|
+
// Default values if not specified
|
|
18
|
+
const color = block.color || '#e6e9ee';
|
|
19
|
+
const height = block.height || 1;
|
|
20
|
+
const margin = block.margin || 16;
|
|
21
|
+
// Inline styles for divider
|
|
22
|
+
const styles = [
|
|
23
|
+
'border: none',
|
|
24
|
+
'border-top: none',
|
|
25
|
+
`border-bottom: ${height}px solid ${color}`,
|
|
26
|
+
`padding-top: ${margin}px`,
|
|
27
|
+
`padding-bottom: ${margin}px`,
|
|
28
|
+
'width: 100%',
|
|
29
|
+
].join(";");
|
|
30
|
+
// HTML table wrapper (email compatibility)
|
|
31
|
+
// HR element doesn't work well in email, so use a table with a line
|
|
32
|
+
return `
|
|
33
|
+
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
34
|
+
<tr>
|
|
35
|
+
<td style="border-bottom: ${height}px solid ${color}; height: 0;"></td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td style="height: ${margin}px;"></td>
|
|
39
|
+
</tr>
|
|
40
|
+
</table>
|
|
41
|
+
`.trim();
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HIGHLIGHT BOX BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single highlight box block to HTML
|
|
5
|
+
* - Receives: HighlightBoxBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for highlight box blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
import type { HighlightBoxBlock } from '../../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Render a highlight box block to HTML
|
|
13
|
+
*
|
|
14
|
+
* @param block - The highlight box block to render
|
|
15
|
+
* @returns HTML string for this block
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderHighlightBlock(block: HighlightBoxBlock): string;
|
|
18
|
+
//# sourceMappingURL=highlight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../../../src/renderer/blocks/highlight.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAqCrE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HIGHLIGHT BOX BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single highlight box block to HTML
|
|
5
|
+
* - Receives: HighlightBoxBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for highlight box blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Render a highlight box block to HTML
|
|
12
|
+
*
|
|
13
|
+
* @param block - The highlight box block to render
|
|
14
|
+
* @returns HTML string for this block
|
|
15
|
+
*/
|
|
16
|
+
export function renderHighlightBlock(block) {
|
|
17
|
+
// Default values if not specified
|
|
18
|
+
const padding = block.padding || '20px';
|
|
19
|
+
const borderRadius = block.borderRadius || 8;
|
|
20
|
+
const paddingBottom = block.paddingBottom || 20;
|
|
21
|
+
const borderColor = block.borderColor || 'transparent';
|
|
22
|
+
// Container styles
|
|
23
|
+
const containerStyles = [
|
|
24
|
+
`background-color: ${block.backgroundColor}`,
|
|
25
|
+
`border-radius: ${borderRadius}px`,
|
|
26
|
+
`padding-bottom: ${paddingBottom}px`,
|
|
27
|
+
'margin-top: 0',
|
|
28
|
+
].join(";");
|
|
29
|
+
// Content styles
|
|
30
|
+
const contentStyles = [
|
|
31
|
+
`padding: ${padding}`,
|
|
32
|
+
'word-wrap: break-word',
|
|
33
|
+
].join(';');
|
|
34
|
+
// Add border if specified
|
|
35
|
+
let borderStyle = '';
|
|
36
|
+
if (block.borderColor && block.borderColor !== 'transparent') {
|
|
37
|
+
borderStyle = `border: 1px solid ${borderColor};`;
|
|
38
|
+
}
|
|
39
|
+
// HTML table wrapper (email compatibility)
|
|
40
|
+
return `
|
|
41
|
+
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="${containerStyles} ${borderStyle}">
|
|
42
|
+
<tr>
|
|
43
|
+
<td style="${contentStyles}">
|
|
44
|
+
${block.content}
|
|
45
|
+
</td>
|
|
46
|
+
</tr>
|
|
47
|
+
</table>
|
|
48
|
+
`.trim();
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IMAGE BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single image block to HTML
|
|
5
|
+
* - Receives: ImageBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for image blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
import type { ImageBlock } from '../../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Render an image block to HTML
|
|
13
|
+
*
|
|
14
|
+
* @param block - The image block to render
|
|
15
|
+
* @returns HTML string for this block
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderImageBlock(block: ImageBlock): string;
|
|
18
|
+
//# sourceMappingURL=image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/renderer/blocks/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAkD1D"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IMAGE BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single image block to HTML
|
|
5
|
+
* - Receives: ImageBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for image blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Render an image block to HTML
|
|
12
|
+
*
|
|
13
|
+
* @param block - The image block to render
|
|
14
|
+
* @returns HTML string for this block
|
|
15
|
+
*/
|
|
16
|
+
export function renderImageBlock(block) {
|
|
17
|
+
// Default values if not specified
|
|
18
|
+
const maxWidth = block.maxWidth || 600;
|
|
19
|
+
const width = block.width || '100%';
|
|
20
|
+
const borderRadius = block.borderRadius || 0;
|
|
21
|
+
const paddingBottom = block.paddingBottom || 16;
|
|
22
|
+
// Build image element
|
|
23
|
+
let imgTag = `<img src="${block.src}" alt="${block.alt}"`;
|
|
24
|
+
// Add width if numeric
|
|
25
|
+
if (typeof block.width === 'number') {
|
|
26
|
+
imgTag += ` width="${block.width}"`;
|
|
27
|
+
}
|
|
28
|
+
// Add height if specified
|
|
29
|
+
if (block.height) {
|
|
30
|
+
imgTag += ` height="${block.height}"`;
|
|
31
|
+
}
|
|
32
|
+
// Inline styles for image
|
|
33
|
+
const imgStyles = [
|
|
34
|
+
`max-width: ${maxWidth}px`,
|
|
35
|
+
'width: 100%',
|
|
36
|
+
'height: auto',
|
|
37
|
+
'display: block',
|
|
38
|
+
borderRadius > 0 ? `border-radius: ${borderRadius}px` : '',
|
|
39
|
+
]
|
|
40
|
+
.filter((s) => s.length > 0)
|
|
41
|
+
.join(';');
|
|
42
|
+
imgTag += ` style="${imgStyles}" />`;
|
|
43
|
+
// Container styles
|
|
44
|
+
const containerStyles = [
|
|
45
|
+
`padding-bottom: ${paddingBottom}px`,
|
|
46
|
+
'margin-top: 0',
|
|
47
|
+
'text-align: center',
|
|
48
|
+
].join(";");
|
|
49
|
+
// HTML table wrapper (email compatibility)
|
|
50
|
+
return `
|
|
51
|
+
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
52
|
+
<tr>
|
|
53
|
+
<td style="${containerStyles}">
|
|
54
|
+
${imgTag}
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
</table>
|
|
58
|
+
`.trim();
|
|
59
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PARAGRAPH BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single paragraph block to HTML
|
|
5
|
+
* - Receives: ParagraphBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for paragraph blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
import type { ParagraphBlock } from '../../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Render a paragraph block to HTML
|
|
13
|
+
*
|
|
14
|
+
* @param block - The paragraph block to render
|
|
15
|
+
* @returns HTML string for this block
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderParagraphBlock(block: ParagraphBlock): string;
|
|
18
|
+
//# sourceMappingURL=paragraph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paragraph.d.ts","sourceRoot":"","sources":["../../../src/renderer/blocks/paragraph.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CA0BlE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PARAGRAPH BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single paragraph block to HTML
|
|
5
|
+
* - Receives: ParagraphBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for paragraph blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
import { parseInlineFormatting } from '../parseInlineFormatting.js';
|
|
11
|
+
/**
|
|
12
|
+
* Render a paragraph block to HTML
|
|
13
|
+
*
|
|
14
|
+
* @param block - The paragraph block to render
|
|
15
|
+
* @returns HTML string for this block
|
|
16
|
+
*/
|
|
17
|
+
export function renderParagraphBlock(block) {
|
|
18
|
+
// Default values if not specified
|
|
19
|
+
const color = block.color || '#334155';
|
|
20
|
+
const lineHeight = block.lineHeight || 1.6;
|
|
21
|
+
const paddingBottom = block.paddingBottom || 16;
|
|
22
|
+
const textAlign = block.textAlign || 'left';
|
|
23
|
+
// Inline styles for paragraph
|
|
24
|
+
const styles = [
|
|
25
|
+
`color: ${color}`,
|
|
26
|
+
`line-height: ${lineHeight}`,
|
|
27
|
+
`padding-bottom: ${paddingBottom}px`,
|
|
28
|
+
'margin-top: 0',
|
|
29
|
+
`text-align: ${textAlign}`,
|
|
30
|
+
].join(";");
|
|
31
|
+
// HTML table wrapper (email compatibility)
|
|
32
|
+
return `
|
|
33
|
+
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
34
|
+
<tr>
|
|
35
|
+
<td style="${styles}">
|
|
36
|
+
${parseInlineFormatting(block.content)}
|
|
37
|
+
</td>
|
|
38
|
+
</tr>
|
|
39
|
+
</table>
|
|
40
|
+
`.trim();
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TITLE BLOCK RENDERER
|
|
3
|
+
*
|
|
4
|
+
* Responsibility: Render a single title block to HTML
|
|
5
|
+
* - Receives: TitleBlock
|
|
6
|
+
* - Returns: HTML string for this block only
|
|
7
|
+
* - Owns: All styling for title blocks
|
|
8
|
+
* - Does NOT: Know about other blocks, templates, or document structure
|
|
9
|
+
*/
|
|
10
|
+
import type { TitleBlock } from '../../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Render a title block to HTML
|
|
13
|
+
*
|
|
14
|
+
* @param block - The title block to render
|
|
15
|
+
* @returns HTML string for this block
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderTitleBlock(block: TitleBlock): string;
|
|
18
|
+
//# sourceMappingURL=title.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"title.d.ts","sourceRoot":"","sources":["../../../src/renderer/blocks/title.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAoC1D"}
|