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.
Files changed (47) hide show
  1. package/README.md +438 -0
  2. package/dist/index.d.ts +127 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +260 -0
  5. package/dist/renderer/blocks/button.d.ts +18 -0
  6. package/dist/renderer/blocks/button.d.ts.map +1 -0
  7. package/dist/renderer/blocks/button.js +57 -0
  8. package/dist/renderer/blocks/divider.d.ts +18 -0
  9. package/dist/renderer/blocks/divider.d.ts.map +1 -0
  10. package/dist/renderer/blocks/divider.js +42 -0
  11. package/dist/renderer/blocks/highlight.d.ts +18 -0
  12. package/dist/renderer/blocks/highlight.d.ts.map +1 -0
  13. package/dist/renderer/blocks/highlight.js +49 -0
  14. package/dist/renderer/blocks/image.d.ts +18 -0
  15. package/dist/renderer/blocks/image.d.ts.map +1 -0
  16. package/dist/renderer/blocks/image.js +59 -0
  17. package/dist/renderer/blocks/paragraph.d.ts +18 -0
  18. package/dist/renderer/blocks/paragraph.d.ts.map +1 -0
  19. package/dist/renderer/blocks/paragraph.js +41 -0
  20. package/dist/renderer/blocks/title.d.ts +18 -0
  21. package/dist/renderer/blocks/title.d.ts.map +1 -0
  22. package/dist/renderer/blocks/title.js +49 -0
  23. package/dist/renderer/parseInlineFormatting.d.ts +14 -0
  24. package/dist/renderer/parseInlineFormatting.d.ts.map +1 -0
  25. package/dist/renderer/parseInlineFormatting.js +178 -0
  26. package/dist/renderer/renderBlock.d.ts +21 -0
  27. package/dist/renderer/renderBlock.d.ts.map +1 -0
  28. package/dist/renderer/renderBlock.js +44 -0
  29. package/dist/renderer/renderEmail.d.ts +26 -0
  30. package/dist/renderer/renderEmail.d.ts.map +1 -0
  31. package/dist/renderer/renderEmail.js +275 -0
  32. package/dist/sanitizer.d.ts +147 -0
  33. package/dist/sanitizer.d.ts.map +1 -0
  34. package/dist/sanitizer.js +533 -0
  35. package/dist/template-config.d.ts +38 -0
  36. package/dist/template-config.d.ts.map +1 -0
  37. package/dist/template-config.js +196 -0
  38. package/dist/test-formatting.d.ts +6 -0
  39. package/dist/test-formatting.d.ts.map +1 -0
  40. package/dist/test-formatting.js +132 -0
  41. package/dist/types.d.ts +243 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +5 -0
  44. package/dist/validator.d.ts +86 -0
  45. package/dist/validator.d.ts.map +1 -0
  46. package/dist/validator.js +435 -0
  47. 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"}