@consilioweb/payload-support 0.9.5 → 0.9.6

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/dist/index.cjs CHANGED
@@ -1457,15 +1457,14 @@ function emailRichContent(html, config) {
1457
1457
  function convertFencedCodeBlocks(input) {
1458
1458
  if (!input.includes("```")) return input;
1459
1459
  const normalized = input.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>\s*<(p|div)[^>]*>/gi, "\n").replace(/<(p|div)[^>]*>/gi, "").replace(/<\/(p|div)>/gi, "\n");
1460
- return normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
1461
- const cleanCode = escapeHtml((code || "").replace(/\n$/, ""));
1462
- const label = lang ? lang.trim() : "code";
1463
- return `<div style="margin: 16px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
1464
- <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
1465
- <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
1466
- </div>`;
1467
- }).split(/(<div style="margin: 16px 0[\s\S]*?<\/div>)/g).map((chunk) => {
1468
- if (chunk.startsWith('<div style="margin: 16px 0')) return chunk;
1460
+ const codeBlocks = [];
1461
+ const withMarkers = normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
1462
+ codeBlocks.push(renderCodeBlockHtml(lang || "", code || ""));
1463
+ return `\0CODEBLOCK_${codeBlocks.length - 1}\0`;
1464
+ });
1465
+ return withMarkers.split(/(\x00CODEBLOCK_\d+\x00)/g).map((chunk) => {
1466
+ const markerMatch = chunk.match(/^\x00CODEBLOCK_(\d+)\x00$/);
1467
+ if (markerMatch) return codeBlocks[parseInt(markerMatch[1], 10)];
1469
1468
  return chunk.split("\n").map((line) => line.trim() ? `<p>${line}</p>` : "").join("");
1470
1469
  }).join("");
1471
1470
  }
@@ -1500,9 +1499,40 @@ function emailButton(text, url, color = "primary", config) {
1500
1499
  </div>
1501
1500
  `;
1502
1501
  }
1502
+ function renderCodeBlockHtml(lang, code) {
1503
+ const cleanCode = escapeHtml(code.replace(/\n$/, ""));
1504
+ const label = lang ? lang.trim() : "code";
1505
+ return `<div style="margin: 12px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
1506
+ <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
1507
+ <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
1508
+ </div>`;
1509
+ }
1503
1510
  function emailQuote(content, borderColor, config) {
1504
1511
  const c = resolveConfig(config);
1505
1512
  const color = borderColor || c.brandColor;
1513
+ if (content && content.includes("```")) {
1514
+ const parts = [];
1515
+ const regex = /```(\w*)\n?([\s\S]*?)```/g;
1516
+ let lastIndex = 0;
1517
+ let match;
1518
+ while ((match = regex.exec(content)) !== null) {
1519
+ const textBefore = content.slice(lastIndex, match.index).trim();
1520
+ if (textBefore) {
1521
+ parts.push(`<p style="margin: 0 0 12px 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(textBefore)}</p>`);
1522
+ }
1523
+ parts.push(renderCodeBlockHtml(match[1] || "", match[2] || ""));
1524
+ lastIndex = match.index + match[0].length;
1525
+ }
1526
+ const textAfter = content.slice(lastIndex).trim();
1527
+ if (textAfter) {
1528
+ parts.push(`<p style="margin: 12px 0 0 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(textAfter)}</p>`);
1529
+ }
1530
+ return `
1531
+ <div style="margin: 24px 0; padding: 20px 24px; background: #f8f9fa; border-left: 4px solid ${color}; border-radius: 0 8px 8px 0;">
1532
+ ${parts.join("")}
1533
+ </div>
1534
+ `;
1535
+ }
1506
1536
  return `
1507
1537
  <div style="margin: 24px 0; padding: 20px 24px; background: #f8f9fa; border-left: 4px solid ${color}; border-radius: 0 8px 8px 0;">
1508
1538
  <p style="margin: 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(content)}</p>
package/dist/index.js CHANGED
@@ -1451,15 +1451,14 @@ function emailRichContent(html, config) {
1451
1451
  function convertFencedCodeBlocks(input) {
1452
1452
  if (!input.includes("```")) return input;
1453
1453
  const normalized = input.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>\s*<(p|div)[^>]*>/gi, "\n").replace(/<(p|div)[^>]*>/gi, "").replace(/<\/(p|div)>/gi, "\n");
1454
- return normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
1455
- const cleanCode = escapeHtml((code || "").replace(/\n$/, ""));
1456
- const label = lang ? lang.trim() : "code";
1457
- return `<div style="margin: 16px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
1458
- <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
1459
- <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
1460
- </div>`;
1461
- }).split(/(<div style="margin: 16px 0[\s\S]*?<\/div>)/g).map((chunk) => {
1462
- if (chunk.startsWith('<div style="margin: 16px 0')) return chunk;
1454
+ const codeBlocks = [];
1455
+ const withMarkers = normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
1456
+ codeBlocks.push(renderCodeBlockHtml(lang || "", code || ""));
1457
+ return `\0CODEBLOCK_${codeBlocks.length - 1}\0`;
1458
+ });
1459
+ return withMarkers.split(/(\x00CODEBLOCK_\d+\x00)/g).map((chunk) => {
1460
+ const markerMatch = chunk.match(/^\x00CODEBLOCK_(\d+)\x00$/);
1461
+ if (markerMatch) return codeBlocks[parseInt(markerMatch[1], 10)];
1463
1462
  return chunk.split("\n").map((line) => line.trim() ? `<p>${line}</p>` : "").join("");
1464
1463
  }).join("");
1465
1464
  }
@@ -1494,9 +1493,40 @@ function emailButton(text, url, color = "primary", config) {
1494
1493
  </div>
1495
1494
  `;
1496
1495
  }
1496
+ function renderCodeBlockHtml(lang, code) {
1497
+ const cleanCode = escapeHtml(code.replace(/\n$/, ""));
1498
+ const label = lang ? lang.trim() : "code";
1499
+ return `<div style="margin: 12px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
1500
+ <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
1501
+ <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
1502
+ </div>`;
1503
+ }
1497
1504
  function emailQuote(content, borderColor, config) {
1498
1505
  const c = resolveConfig(config);
1499
1506
  const color = borderColor || c.brandColor;
1507
+ if (content && content.includes("```")) {
1508
+ const parts = [];
1509
+ const regex = /```(\w*)\n?([\s\S]*?)```/g;
1510
+ let lastIndex = 0;
1511
+ let match;
1512
+ while ((match = regex.exec(content)) !== null) {
1513
+ const textBefore = content.slice(lastIndex, match.index).trim();
1514
+ if (textBefore) {
1515
+ parts.push(`<p style="margin: 0 0 12px 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(textBefore)}</p>`);
1516
+ }
1517
+ parts.push(renderCodeBlockHtml(match[1] || "", match[2] || ""));
1518
+ lastIndex = match.index + match[0].length;
1519
+ }
1520
+ const textAfter = content.slice(lastIndex).trim();
1521
+ if (textAfter) {
1522
+ parts.push(`<p style="margin: 12px 0 0 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(textAfter)}</p>`);
1523
+ }
1524
+ return `
1525
+ <div style="margin: 24px 0; padding: 20px 24px; background: #f8f9fa; border-left: 4px solid ${color}; border-radius: 0 8px 8px 0;">
1526
+ ${parts.join("")}
1527
+ </div>
1528
+ `;
1529
+ }
1500
1530
  return `
1501
1531
  <div style="margin: 24px 0; padding: 20px 24px; background: #f8f9fa; border-left: 4px solid ${color}; border-radius: 0 8px 8px 0;">
1502
1532
  <p style="margin: 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(content)}</p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consilioweb/payload-support",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -88,19 +88,19 @@ export function emailRichContent(html: string, config?: EmailTemplateConfig): st
88
88
  .replace(/<(p|div)[^>]*>/gi, '')
89
89
  .replace(/<\/(p|div)>/gi, '\n')
90
90
 
91
- return normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
92
- const cleanCode = escapeHtml((code || '').replace(/\n$/, ''))
93
- const label = lang ? lang.trim() : 'code'
94
- return `<div style="margin: 16px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
95
- <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
96
- <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
97
- </div>`
91
+ // Replace code blocks with markers to protect them from paragraph wrapping
92
+ const codeBlocks: string[] = []
93
+ const withMarkers = normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
94
+ codeBlocks.push(renderCodeBlockHtml(lang || '', code || ''))
95
+ return `\x00CODEBLOCK_${codeBlocks.length - 1}\x00`
98
96
  })
99
- // Re-wrap non-code text chunks in paragraphs (split by our <div> blocks)
100
- .split(/(<div style="margin: 16px 0[\s\S]*?<\/div>)/g)
97
+
98
+ // Wrap non-marker text lines in <p>, then restore code blocks
99
+ return withMarkers
100
+ .split(/(\x00CODEBLOCK_\d+\x00)/g)
101
101
  .map((chunk) => {
102
- if (chunk.startsWith('<div style="margin: 16px 0')) return chunk
103
- // Non-code chunk: wrap text lines in <p>
102
+ const markerMatch = chunk.match(/^\x00CODEBLOCK_(\d+)\x00$/)
103
+ if (markerMatch) return codeBlocks[parseInt(markerMatch[1], 10)]
104
104
  return chunk
105
105
  .split('\n')
106
106
  .map((line) => line.trim() ? `<p>${line}</p>` : '')
@@ -165,11 +165,52 @@ export function emailButton(text: string, url: string, color: ButtonColor = 'pri
165
165
  }
166
166
 
167
167
  /**
168
- * Quote block for message previews
168
+ * Render a single fenced code block as styled email HTML.
169
+ * Email clients don't run JS so we just use a nice static layout.
170
+ */
171
+ function renderCodeBlockHtml(lang: string, code: string): string {
172
+ const cleanCode = escapeHtml(code.replace(/\n$/, ''))
173
+ const label = lang ? lang.trim() : 'code'
174
+ return `<div style="margin: 12px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
175
+ <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
176
+ <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
177
+ </div>`
178
+ }
179
+
180
+ /**
181
+ * Quote block for message previews — supports fenced code blocks.
169
182
  */
170
183
  export function emailQuote(content: string, borderColor?: string, config?: EmailTemplateConfig): string {
171
184
  const c = resolveConfig(config)
172
185
  const color = borderColor || c.brandColor
186
+
187
+ // If content has fenced code blocks, render them as styled blocks
188
+ if (content && content.includes('```')) {
189
+ const parts: string[] = []
190
+ const regex = /```(\w*)\n?([\s\S]*?)```/g
191
+ let lastIndex = 0
192
+ let match: RegExpExecArray | null
193
+ while ((match = regex.exec(content)) !== null) {
194
+ // Text before the code block
195
+ const textBefore = content.slice(lastIndex, match.index).trim()
196
+ if (textBefore) {
197
+ parts.push(`<p style="margin: 0 0 12px 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(textBefore)}</p>`)
198
+ }
199
+ parts.push(renderCodeBlockHtml(match[1] || '', match[2] || ''))
200
+ lastIndex = match.index + match[0].length
201
+ }
202
+ // Remaining text after the last code block
203
+ const textAfter = content.slice(lastIndex).trim()
204
+ if (textAfter) {
205
+ parts.push(`<p style="margin: 12px 0 0 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(textAfter)}</p>`)
206
+ }
207
+ return `
208
+ <div style="margin: 24px 0; padding: 20px 24px; background: #f8f9fa; border-left: 4px solid ${color}; border-radius: 0 8px 8px 0;">
209
+ ${parts.join('')}
210
+ </div>
211
+ `
212
+ }
213
+
173
214
  return `
174
215
  <div style="margin: 24px 0; padding: 20px 24px; background: #f8f9fa; border-left: 4px solid ${color}; border-radius: 0 8px 8px 0;">
175
216
  <p style="margin: 0; font-size: 15px; line-height: 1.75; color: #333333; white-space: pre-wrap;">${escapeHtml(content)}</p>