feat: reusable badge templates, dynamic price and superscript cents, size/align/padding controls, fix saving
This commit is contained in:
1 parent
f9aa2297ba
commit
5a6788b593
6 files changed
+1196
-359
No files matched your search
@@ -0,0 +1,324 @@
|
||||
import { BadgeTemplate, BadgeLayer } from '@/types/newsletter';
|
||||
|
||||
export const DEFAULT_BADGE_TEMPLATES: BadgeTemplate[] = [
|
||||
{
|
||||
id: 'badge-preset-yellow-akcia',
|
||||
name: 'COOP Akcia (Žltá)',
|
||||
svgRaw: `<svg width="140" height="95" viewBox="0 0 140 95" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="140" height="95" rx="14" fill="#ffd100"/>
|
||||
<rect x="15" y="8" width="110" height="24" rx="6" fill="#e2001a"/>
|
||||
</svg>`,
|
||||
width: 110,
|
||||
align: 'center',
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
layersConfig: [
|
||||
{
|
||||
id: 'l-akcia-1',
|
||||
type: 'text',
|
||||
text: 'AKCIA',
|
||||
x: 70,
|
||||
y: 20,
|
||||
fontSize: 14,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Poppins',
|
||||
fill: '#ffffff',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
id: 'l-akcia-2',
|
||||
type: 'text',
|
||||
text: '{cena}',
|
||||
x: 70,
|
||||
y: 62,
|
||||
fontSize: 42,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Arial Narrow',
|
||||
fill: '#001a33',
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'badge-preset-red-zlava',
|
||||
name: 'COOP Zľava (Červená)',
|
||||
svgRaw: `<svg width="140" height="95" viewBox="0 0 140 95" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="140" height="95" rx="14" fill="#e2001a"/>
|
||||
<rect x="12" y="8" width="116" height="22" rx="5" fill="#ffffff" fill-opacity="0.2"/>
|
||||
</svg>`,
|
||||
width: 110,
|
||||
align: 'center',
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
layersConfig: [
|
||||
{
|
||||
id: 'l-zlava-1',
|
||||
type: 'text',
|
||||
text: 'ZĽAVA -{zlava}',
|
||||
x: 70,
|
||||
y: 19,
|
||||
fontSize: 13,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Poppins',
|
||||
fill: '#ffffff',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
id: 'l-zlava-2',
|
||||
type: 'text',
|
||||
text: '{cena}',
|
||||
x: 70,
|
||||
y: 62,
|
||||
fontSize: 42,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Arial Narrow',
|
||||
fill: '#ffd100',
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'badge-preset-blue-supercena',
|
||||
name: 'Super Cena (Tmavomodrá)',
|
||||
svgRaw: `<svg width="140" height="95" viewBox="0 0 140 95" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="140" height="95" rx="14" fill="#001a33"/>
|
||||
<rect x="0" y="0" width="140" height="30" rx="14" fill="#003399"/>
|
||||
</svg>`,
|
||||
width: 110,
|
||||
align: 'center',
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
layersConfig: [
|
||||
{
|
||||
id: 'l-super-1',
|
||||
type: 'text',
|
||||
text: 'SUPER CENA',
|
||||
x: 70,
|
||||
y: 16,
|
||||
fontSize: 13,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Poppins',
|
||||
fill: '#ffffff',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
id: 'l-super-2',
|
||||
type: 'text',
|
||||
text: '{cena}',
|
||||
x: 70,
|
||||
y: 62,
|
||||
fontSize: 42,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Arial Narrow',
|
||||
fill: '#ffd100',
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'badge-preset-green-cerstve',
|
||||
name: 'Bio & Čerstvé (Zelená)',
|
||||
svgRaw: `<svg width="140" height="95" viewBox="0 0 140 95" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="140" height="95" rx="14" fill="#005239"/>
|
||||
<rect x="15" y="8" width="110" height="22" rx="6" fill="#003d2b"/>
|
||||
</svg>`,
|
||||
width: 110,
|
||||
align: 'center',
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
layersConfig: [
|
||||
{
|
||||
id: 'l-green-1',
|
||||
type: 'text',
|
||||
text: 'ČERSTVÉ',
|
||||
x: 70,
|
||||
y: 19,
|
||||
fontSize: 13,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Poppins',
|
||||
fill: '#a3e635',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
id: 'l-green-2',
|
||||
type: 'text',
|
||||
text: '{cena}',
|
||||
x: 70,
|
||||
y: 62,
|
||||
fontSize: 42,
|
||||
fontWeight: '900',
|
||||
fontFamily: 'Arial Narrow',
|
||||
fill: '#ffffff',
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export interface BadgeProductData {
|
||||
priceMain: string;
|
||||
priceSub: string;
|
||||
discountText?: string;
|
||||
unitPrice?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vygeneruje 100% čistý vektorový SVG reťazec cenovky bez akýchkoľvek
|
||||
* editovacích rámčekov, tooltippov či ovládacích prvkov.
|
||||
* Centy sú naformátované ako horný index (tspan dy="-0.45em").
|
||||
* Cena je predvolene bez znaku €.
|
||||
*/
|
||||
export function renderCleanBadgeSvg(
|
||||
template: BadgeTemplate,
|
||||
product: BadgeProductData,
|
||||
overrideWidth?: number
|
||||
): string {
|
||||
const pMain = product.priceMain || '0';
|
||||
const pSub = product.priceSub || '00';
|
||||
const discount = (product.discountText || '').replace('-', '');
|
||||
const unitPrice = product.unitPrice || '';
|
||||
|
||||
// Získanie viewBox alebo predvolených rozmerov
|
||||
let vb = '0 0 140 95';
|
||||
let vbWidth = 140;
|
||||
let vbHeight = 95;
|
||||
|
||||
if (template.svgRaw) {
|
||||
const vbMatch = template.svgRaw.match(/viewBox=["']([^"']+)["']/i);
|
||||
if (vbMatch) {
|
||||
vb = vbMatch[1];
|
||||
const parts = vb.trim().split(/\s+/).map(Number);
|
||||
if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
|
||||
vbWidth = parts[2];
|
||||
vbHeight = parts[3];
|
||||
}
|
||||
} else {
|
||||
const wMatch = template.svgRaw.match(/width=["']([^"']+)["']/i);
|
||||
const hMatch = template.svgRaw.match(/height=["']([^"']+)["']/i);
|
||||
if (wMatch && hMatch) {
|
||||
const w = parseFloat(wMatch[1]);
|
||||
const h = parseFloat(hMatch[1]);
|
||||
if (w > 0 && h > 0) {
|
||||
vbWidth = w;
|
||||
vbHeight = h;
|
||||
vb = `0 0 ${w} ${h}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extrahovanie vnútorného obsahu SVG (medzi <svg...> a </svg>)
|
||||
let innerBg = '';
|
||||
if (template.svgRaw) {
|
||||
const inside = template.svgRaw.replace(/<\/?svg[^>]*>/gi, '').trim();
|
||||
innerBg = inside;
|
||||
} else {
|
||||
innerBg = `<rect width="${vbWidth}" height="${vbHeight}" rx="14" fill="#ffd100"/>`;
|
||||
}
|
||||
|
||||
// Šírka výsledného SVG
|
||||
const displayWidth = overrideWidth || template.width || 110;
|
||||
const displayHeight = Math.round((displayWidth / vbWidth) * vbHeight);
|
||||
|
||||
// Zostavenie textových vrstiev
|
||||
const textLayersSvg = (template.layersConfig || []).map((layer) => {
|
||||
const rawText = layer.text || '';
|
||||
const fontSize = layer.fontSize || 16;
|
||||
const subFontSize = Math.max(10, Math.round(fontSize * 0.55));
|
||||
const fontWeight = layer.fontWeight || '700';
|
||||
const fill = layer.fill || '#ffffff';
|
||||
const fontFamily = layer.fontFamily
|
||||
? `'${layer.fontFamily}', -apple-system, BlinkMacSystemFont, Arial, sans-serif`
|
||||
: `'Arial Narrow', Arial, sans-serif`;
|
||||
|
||||
let anchor = 'middle';
|
||||
if (layer.align === 'left') anchor = 'start';
|
||||
if (layer.align === 'right') anchor = 'end';
|
||||
|
||||
// Formátovanie textu: ak obsahuje {cena}, centy vložíme do <tspan> ako horný index
|
||||
let contentSvg = '';
|
||||
if (rawText.includes('{cena}')) {
|
||||
const parts = rawText.split('{cena}');
|
||||
const before = escapeXml(parts[0] || '');
|
||||
const after = escapeXml(parts[1] || '');
|
||||
contentSvg = `${before}${escapeXml(pMain)}<tspan font-size="${subFontSize}px" dy="-0.45em" font-weight="${fontWeight}">${escapeXml(pSub)}</tspan><tspan dy="0.45em">${after}</tspan>`;
|
||||
} else {
|
||||
let replaced = rawText
|
||||
.replace(/{cela_suma}/g, pMain)
|
||||
.replace(/{centy}/g, pSub)
|
||||
.replace(/{zlava}/g, discount)
|
||||
.replace(/{jednotkova_cena}/g, unitPrice)
|
||||
.replace(/{mena}/g, '€');
|
||||
contentSvg = escapeXml(replaced);
|
||||
}
|
||||
|
||||
return `<text x="${layer.x}" y="${layer.y}" text-anchor="${anchor}" dominant-baseline="central" font-family="${fontFamily}" font-size="${fontSize}px" font-weight="${fontWeight}" fill="${fill}">${contentSvg}</text>`;
|
||||
}).join('\n ');
|
||||
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${vb}" width="${displayWidth}" height="${displayHeight}" style="display:block;max-width:100%;height:auto;">
|
||||
${innerBg}
|
||||
${textLayersSvg}
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skonvertuje SVG reťazec do čistého PNG Data URL v prehliadači
|
||||
* (bez akýchkoľvek DOM prvkov alebo modrých drag & drop rámčekov).
|
||||
*/
|
||||
export async function svgToCleanPng(
|
||||
svgString: string,
|
||||
width: number,
|
||||
height: number
|
||||
): Promise<string> {
|
||||
if (typeof window === 'undefined') {
|
||||
return `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const img = new Image();
|
||||
const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
const scale = 2; // Vysoké rozlíšenie Retina
|
||||
canvas.width = width * scale;
|
||||
canvas.height = height * scale;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
URL.revokeObjectURL(url);
|
||||
resolve(canvas.toDataURL('image/png'));
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Canvas export fallback to SVG data URL', e);
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
resolve(`data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
resolve(`data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`);
|
||||
};
|
||||
|
||||
img.src = url;
|
||||
} catch (err) {
|
||||
resolve(`data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function escapeXml(unsafe: string): string {
|
||||
if (!unsafe) return '';
|
||||
return unsafe
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
+81
-40
@@ -1,5 +1,6 @@
|
||||
import { NewsletterCampaign, ProductItem } from '@/types/newsletter';
|
||||
import { SUPABASE_ASSETS_BASE } from './defaultCampaign';
|
||||
import { renderCleanBadgeSvg } from './badgeUtils';
|
||||
|
||||
export function renderEmailHtml(campaign: NewsletterCampaign): string {
|
||||
const renderedModules = campaign.modules.map((m) => renderModule(m, campaign)).join('\n');
|
||||
@@ -620,46 +621,86 @@ function renderExactProductCard(
|
||||
</tr>
|
||||
<tr height="1">
|
||||
<td style="padding: 0 8px 12px 8px; text-align: center;" valign="bottom">
|
||||
<!-- Vyladená Cenovka Premium v8/v11 1:1 alebo vyrenderovaná PNG cenovka z editora -->
|
||||
${p.customBadgePngUrl ? `
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: 0 auto;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="${escapeHtml(p.customBadgePngUrl)}" width="110" alt="Cenovka" style="display: block; margin: 0 auto; max-width: 120px;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>` : `
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="width: 95px; margin: 0 auto;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" style="background-color: ${badgeHdrBg}; border: 0; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.18); width: 85px; overflow: hidden;">
|
||||
${badgeCustomTxt ? `
|
||||
<tr>
|
||||
<td bgcolor="${badgeHdrBg}" style="padding: 4px 4px; text-align: center; background-color: ${badgeHdrBg};">
|
||||
<span style="font-family: 'Arial Narrow', Arial, sans-serif; font-size: 10px; color: ${badgeHdrColor}; font-weight: 800; text-transform: uppercase;">${escapeHtml(badgeCustomTxt)}</span>
|
||||
</td>
|
||||
</tr>` : (p.discountText ? `
|
||||
<tr>
|
||||
<td style="padding: 2px 4px; text-align: right; background-color: ${badgeHdrBg};">
|
||||
<span style="font-family: 'Arial Narrow', Arial, sans-serif; font-size: 9px; color: ${badgeHdrColor}; font-weight: 700; text-transform: lowercase;">${escapeHtml(p.badgeText || 'zľava')}</span>
|
||||
<span style="font-family: 'Arial Narrow', Arial, sans-serif; font-size: 20px; color: ${badgeHdrColor}; font-weight: 900; line-height: 1; margin-left: 2px;">${escapeHtml(p.discountText)}</span>
|
||||
</td>
|
||||
</tr>` : '')}
|
||||
<tr>
|
||||
<td class="force-text-white" bgcolor="${badgePrcBg}" style="background-color: ${badgePrcBg}; padding: 4px 2px 2px 2px; font-family: 'Arial Narrow', Arial, sans-serif; color: ${badgePrcColor}; font-weight: 900; line-height: 1; text-align: center; border-radius: 0 0 12px 12px;">
|
||||
<span style="font-size: 52px;">${escapeHtml(p.priceMain)}</span><sup style="font-size: 26px; vertical-align: top; line-height: 1.2;">${escapeHtml(p.priceSub)}</sup>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
${p.showSlovakFlag ? `
|
||||
<tr>
|
||||
<td align="right" style="line-height: 0; padding: 0;">
|
||||
<img src="${SUPABASE_ASSETS_BASE}vlajka_srdce.png" width="40" alt="SK" style="display: block; margin-top: -26px; margin-right: -10px; padding: 4px; filter: drop-shadow(1px 0 0 #fefefe) drop-shadow(-1px 0 0 #fefefe) drop-shadow(0 1px 0 #fefefe) drop-shadow(0 -1px 0 #fefefe);">
|
||||
</td>
|
||||
</tr>` : ''}
|
||||
</table>`}
|
||||
${(() => {
|
||||
const bWidth = p.badgeWidth || (p.badgeCustomTemplate ? (p.badgeCustomTemplate.width || 110) : 95);
|
||||
const bAlign = p.badgeAlign || (p.badgeCustomTemplate ? (p.badgeCustomTemplate.align || 'center') : 'center');
|
||||
const padTop = p.badgePaddingTop ?? (p.badgeCustomTemplate?.paddingTop ?? 0);
|
||||
const padBottom = p.badgePaddingBottom ?? (p.badgeCustomTemplate?.paddingBottom ?? 0);
|
||||
|
||||
let marginStyle = 'margin: 0 auto;';
|
||||
if (bAlign === 'left') {
|
||||
marginStyle = 'margin: 0 auto 0 0; margin-right: auto; margin-left: 0;';
|
||||
} else if (bAlign === 'right') {
|
||||
marginStyle = 'margin: 0 0 0 auto; margin-left: auto; margin-right: 0;';
|
||||
}
|
||||
|
||||
// 1. Ak produkt má vlastnú dynamickú šablónu cenovky (čistý SVG vektor bez editovacích prvkov a so živou cenou)
|
||||
if (p.badgeCustomTemplate) {
|
||||
return `
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
||||
<tr>
|
||||
<td align="${bAlign}">
|
||||
${renderCleanBadgeSvg(p.badgeCustomTemplate, p, bWidth)}
|
||||
</td>
|
||||
</tr>
|
||||
${p.showSlovakFlag ? `
|
||||
<tr>
|
||||
<td align="right" style="line-height: 0; padding: 0;">
|
||||
<img src="${SUPABASE_ASSETS_BASE}vlajka_srdce.png" width="40" alt="SK" style="display: block; margin-top: -26px; margin-right: -10px; padding: 4px; filter: drop-shadow(1px 0 0 #fefefe) drop-shadow(-1px 0 0 #fefefe) drop-shadow(0 1px 0 #fefefe) drop-shadow(0 -1px 0 #fefefe);">
|
||||
</td>
|
||||
</tr>` : ''}
|
||||
</table>`;
|
||||
}
|
||||
|
||||
// 2. Ak je nahraná statická PNG cenovka
|
||||
if (p.customBadgePngUrl) {
|
||||
return `
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
||||
<tr>
|
||||
<td align="${bAlign}">
|
||||
<img src="${escapeHtml(p.customBadgePngUrl)}" width="${bWidth}" alt="Cenovka" style="display: block; width: 100%; max-width: ${bWidth}px; margin: 0 auto;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
// 3. Predvolená COOP cenovka s plnou podporou šírky, zarovnania a odsadenia
|
||||
const mainFontSize = Math.max(28, Math.round(bWidth * 0.54));
|
||||
const subFontSize = Math.max(14, Math.round(bWidth * 0.27));
|
||||
|
||||
return `
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
||||
<tr>
|
||||
<td align="${bAlign}">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" style="background-color: ${badgeHdrBg}; border: 0; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.18); width: ${bWidth}px; overflow: hidden;">
|
||||
${badgeCustomTxt ? `
|
||||
<tr>
|
||||
<td bgcolor="${badgeHdrBg}" style="padding: 4px 4px; text-align: center; background-color: ${badgeHdrBg};">
|
||||
<span style="font-family: 'Arial Narrow', Arial, sans-serif; font-size: 10px; color: ${badgeHdrColor}; font-weight: 800; text-transform: uppercase;">${escapeHtml(badgeCustomTxt)}</span>
|
||||
</td>
|
||||
</tr>` : (p.discountText ? `
|
||||
<tr>
|
||||
<td style="padding: 2px 4px; text-align: right; background-color: ${badgeHdrBg};">
|
||||
<span style="font-family: 'Arial Narrow', Arial, sans-serif; font-size: 9px; color: ${badgeHdrColor}; font-weight: 700; text-transform: lowercase;">${escapeHtml(p.badgeText || 'zľava')}</span>
|
||||
<span style="font-family: 'Arial Narrow', Arial, sans-serif; font-size: 20px; color: ${badgeHdrColor}; font-weight: 900; line-height: 1; margin-left: 2px;">${escapeHtml(p.discountText)}</span>
|
||||
</td>
|
||||
</tr>` : '')}
|
||||
<tr>
|
||||
<td class="force-text-white" bgcolor="${badgePrcBg}" style="background-color: ${badgePrcBg}; padding: 4px 2px 2px 2px; font-family: 'Arial Narrow', Arial, sans-serif; color: ${badgePrcColor}; font-weight: 900; line-height: 1; text-align: center; border-radius: 0 0 12px 12px;">
|
||||
<span style="font-size: ${mainFontSize}px;">${escapeHtml(p.priceMain)}</span><sup style="font-size: ${subFontSize}px; vertical-align: top; line-height: 1.2;">${escapeHtml(p.priceSub)}</sup>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
${p.showSlovakFlag ? `
|
||||
<tr>
|
||||
<td align="right" style="line-height: 0; padding: 0;">
|
||||
<img src="${SUPABASE_ASSETS_BASE}vlajka_srdce.png" width="40" alt="SK" style="display: block; margin-top: -26px; margin-right: -10px; padding: 4px; filter: drop-shadow(1px 0 0 #fefefe) drop-shadow(-1px 0 0 #fefefe) drop-shadow(0 1px 0 #fefefe) drop-shadow(0 -1px 0 #fefefe);">
|
||||
</td>
|
||||
</tr>` : ''}
|
||||
</table>`;
|
||||
})()}
|
||||
${p.unitPrice ? `<div style="font-size: 9px; color: #666666; margin-top: 4px; font-weight: 500; font-family: Arial, sans-serif;">${escapeHtml(p.unitPrice)}</div>` : ''}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in new issue
Block a user