596 lines
22 KiB
TypeScript
596 lines
22 KiB
TypeScript
import {
|
|
PersonalityDataType,
|
|
HorizontalAlignment,
|
|
VerticalAlignment,
|
|
ButtonSize,
|
|
ImageSize,
|
|
TextType,
|
|
ButtonType,
|
|
ImageType,
|
|
SectionType,
|
|
SocialType,
|
|
SocialNetworkType,
|
|
SocialIconSize,
|
|
SocialIconStyle,
|
|
} from "../types/Types";
|
|
import {
|
|
getSocialIconPath,
|
|
hasPngIcon,
|
|
getSocialColor,
|
|
getSocialSvgIcon,
|
|
} from "./socialIcons";
|
|
|
|
export const exportPersonalityToHTML = async (
|
|
data: PersonalityDataType
|
|
): Promise<string> => {
|
|
console.log(
|
|
"🔥 [ExportHTML] Starting export with data:",
|
|
JSON.stringify(data, null, 2)
|
|
);
|
|
// Helper functions
|
|
const getHorizontalAlign = (alignment?: HorizontalAlignment) => {
|
|
switch (alignment) {
|
|
case HorizontalAlignment.LEFT:
|
|
return "left";
|
|
case HorizontalAlignment.RIGHT:
|
|
return "right";
|
|
case HorizontalAlignment.CENTER:
|
|
default:
|
|
return "center";
|
|
}
|
|
};
|
|
|
|
const getVerticalAlign = (verticalAlignment?: VerticalAlignment) => {
|
|
switch (verticalAlignment) {
|
|
case VerticalAlignment.TOP:
|
|
return "top";
|
|
case VerticalAlignment.BOTTOM:
|
|
return "bottom";
|
|
case VerticalAlignment.MIDDLE:
|
|
default:
|
|
return "middle";
|
|
}
|
|
};
|
|
|
|
// Render TextRenderer equivalent
|
|
const renderTexts = (texts: TextType[]) => {
|
|
console.log("🔥 [ExportHTML] renderTexts called with:", texts);
|
|
if (!texts || texts.length === 0) {
|
|
console.log("🔥 [ExportHTML] No texts to render");
|
|
return "";
|
|
}
|
|
|
|
return texts
|
|
.map(
|
|
(textItem, textIndex) =>
|
|
`<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse;">
|
|
<tbody>
|
|
<tr>
|
|
<td align="${textItem.alignment || "right"}" style="color: ${
|
|
textItem.color || "#000000"
|
|
}; font-size: ${
|
|
textItem.fontSize || 14
|
|
}px; line-height: 1.4; padding-bottom: ${
|
|
textIndex < (texts?.length || 0) - 1 ? "4px" : "0"
|
|
};">
|
|
${textItem.text}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>`
|
|
)
|
|
.join("");
|
|
};
|
|
|
|
// Render ButtonRenderer equivalent
|
|
const renderButtons = (buttons: ButtonType[]) => {
|
|
console.log("🔥 [ExportHTML] renderButtons called with:", buttons);
|
|
if (!buttons || buttons.length === 0) {
|
|
console.log("🔥 [ExportHTML] No buttons to render");
|
|
return "";
|
|
}
|
|
|
|
return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse;">
|
|
<tbody>
|
|
<tr>
|
|
<td align="${getHorizontalAlign(
|
|
buttons[0]?.alignment
|
|
)}" valign="${getVerticalAlign(
|
|
buttons[0]?.verticalAlignment
|
|
)}" style="width: 100%;">
|
|
<table cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse;">
|
|
<tbody>
|
|
<tr>
|
|
${buttons
|
|
.slice(0, 2)
|
|
.map(
|
|
(button, buttonIndex) =>
|
|
`<td style="padding-right: ${
|
|
buttonIndex === 0 && buttons.length > 1 ? "4px" : "0"
|
|
};">
|
|
<table cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse; background-color: ${
|
|
button.backgroundColor
|
|
}; border: ${
|
|
button.isBorder
|
|
? `${button.borderSize}px solid ${button.borderColor}`
|
|
: "none"
|
|
}; border-radius: 4px;">
|
|
<tbody>
|
|
<tr>
|
|
<td align="center" valign="middle" style="padding: ${
|
|
button.size === ButtonSize.SMALL
|
|
? "2px 4px"
|
|
: button.size === ButtonSize.MEDIUM
|
|
? "4px 8px"
|
|
: "8px 12px"
|
|
}; font-size: ${
|
|
button.size === ButtonSize.SMALL
|
|
? "11px"
|
|
: button.size === ButtonSize.MEDIUM
|
|
? "13px"
|
|
: "15px"
|
|
}; color: ${
|
|
button.textColor
|
|
}; text-decoration: none; white-space: nowrap; max-width: fit-content; overflow: hidden; text-overflow: ellipsis;">
|
|
<a href="${button.link || "#"}" style="color: ${
|
|
button.textColor
|
|
}; text-decoration: none;">
|
|
${button.text}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</td>`
|
|
)
|
|
.join("")}
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>`;
|
|
};
|
|
|
|
// Render ImageRenderer equivalent
|
|
const renderImages = (images: ImageType[]) => {
|
|
console.log("🔥 [ExportHTML] renderImages called with:", images);
|
|
if (!images || images.length === 0) {
|
|
console.log("🔥 [ExportHTML] No images to render");
|
|
return "";
|
|
}
|
|
|
|
const shouldUseBackgroundImage = (imageSize: ImageSize) => {
|
|
return (
|
|
imageSize === ImageSize.REPEAT ||
|
|
imageSize === ImageSize.REPEAT_X ||
|
|
imageSize === ImageSize.REPEAT_Y
|
|
);
|
|
};
|
|
|
|
const getBackgroundStyle = (image: ImageType) => {
|
|
let backgroundRepeat = "no-repeat";
|
|
|
|
switch (image.size) {
|
|
case ImageSize.REPEAT:
|
|
backgroundRepeat = "repeat";
|
|
break;
|
|
case ImageSize.REPEAT_X:
|
|
backgroundRepeat = "repeat-x";
|
|
break;
|
|
case ImageSize.REPEAT_Y:
|
|
backgroundRepeat = "repeat-y";
|
|
break;
|
|
default:
|
|
backgroundRepeat = "no-repeat";
|
|
break;
|
|
}
|
|
|
|
const horizontal =
|
|
image.alignment === HorizontalAlignment.LEFT
|
|
? "left"
|
|
: image.alignment === HorizontalAlignment.RIGHT
|
|
? "right"
|
|
: "center";
|
|
const vertical =
|
|
image.verticalAlignment === VerticalAlignment.TOP
|
|
? "top"
|
|
: image.verticalAlignment === VerticalAlignment.BOTTOM
|
|
? "bottom"
|
|
: "center";
|
|
|
|
return `background-image: url(${
|
|
image.src
|
|
}); background-position: ${horizontal} ${vertical}; background-repeat: ${backgroundRepeat}; width: ${
|
|
image.width || "100%"
|
|
}; height: ${image.height || "100px"};`;
|
|
};
|
|
|
|
const renderImage = (image: ImageType) => {
|
|
if (shouldUseBackgroundImage(image.size)) {
|
|
return `<div style="${getBackgroundStyle(image)}"> </div>`;
|
|
}
|
|
|
|
if (image.size === ImageSize.CONTAIN) {
|
|
return `<table width="${
|
|
image.width || "100%"
|
|
}" cellpadding="0" cellspacing="0" border="0">
|
|
<tbody>
|
|
<tr>
|
|
<td align="center" valign="middle" style="text-align: right;">
|
|
<img src="${image.src}" alt="${
|
|
image.alt || "تصویر آپلود شده"
|
|
}" width="${image.width || "100%"}" height="${
|
|
image.height || "auto"
|
|
}" style="border: 0;" />
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>`;
|
|
}
|
|
|
|
return `<img src="${image.src}" alt="${
|
|
image.alt || "تصویر آپلود شده"
|
|
}" width="${image.width || "100%"}" height="${
|
|
image.height || "auto"
|
|
}" style="border: 0;" />`;
|
|
};
|
|
|
|
return images
|
|
.map(
|
|
(image, imageIndex) =>
|
|
`<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
<tbody>
|
|
<tr>
|
|
<td align="${getHorizontalAlign(
|
|
image.alignment
|
|
)}" valign="${getVerticalAlign(
|
|
image.verticalAlignment
|
|
)}" style="padding-bottom: ${
|
|
imageIndex < images.length - 1 ? "4px" : "0"
|
|
};">
|
|
${renderImage(image)}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>`
|
|
)
|
|
.join("");
|
|
};
|
|
|
|
// Render SocialRenderer equivalent
|
|
const renderSocials = (socials: SocialType[]) => {
|
|
console.log("🔥 [ExportHTML] renderSocials called with:", socials);
|
|
if (!socials || socials.length === 0) {
|
|
console.log("🔥 [ExportHTML] No socials to render");
|
|
return "";
|
|
}
|
|
|
|
const getIconSize = (size: SocialIconSize) => {
|
|
switch (size) {
|
|
case SocialIconSize.SMALL:
|
|
return "24px";
|
|
case SocialIconSize.MEDIUM:
|
|
return "32px";
|
|
case SocialIconSize.LARGE:
|
|
return "40px";
|
|
default:
|
|
return "32px";
|
|
}
|
|
};
|
|
|
|
const getIconStyle = (social: SocialType) => {
|
|
const size = getIconSize(social.size);
|
|
const baseStyle = `width: ${size}; height: ${size}; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; color: ${
|
|
social.color || getSocialColor(social.networkType)
|
|
}; margin: 2px;`;
|
|
|
|
switch (social.style) {
|
|
case SocialIconStyle.CIRCLE:
|
|
return `${baseStyle} border-radius: 50%;`;
|
|
case SocialIconStyle.SQUARE:
|
|
return `${baseStyle} border-radius: 0;`;
|
|
case SocialIconStyle.ROUNDED:
|
|
return `${baseStyle} border-radius: 8px;`;
|
|
default:
|
|
return baseStyle;
|
|
}
|
|
};
|
|
|
|
const getSocialIcon = (networkType: SocialNetworkType) => {
|
|
// Check if PNG icon is available, if so use it
|
|
if (hasPngIcon(networkType)) {
|
|
const iconPath = getSocialIconPath(networkType);
|
|
return `<img src="${iconPath}" alt="${networkType} icon" style="width: 100%; height: 100%; object-fit: contain; display: block;" />`;
|
|
}
|
|
|
|
// Use shared SVG function
|
|
return getSocialSvgIcon(networkType, false) as string;
|
|
};
|
|
|
|
return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse;">
|
|
<tbody>
|
|
<tr>
|
|
<td align="${getHorizontalAlign(
|
|
socials[0]?.alignment
|
|
)}" valign="${getVerticalAlign(
|
|
socials[0]?.verticalAlignment
|
|
)}" style="padding: 4px 0;">
|
|
<div style="display: flex; justify-content: ${getHorizontalAlign(
|
|
socials[0]?.alignment
|
|
)}; gap: 4px; flex-wrap: wrap;">
|
|
${socials
|
|
.map(
|
|
(social) =>
|
|
`<a href="${social.link || "#"}" style="${getIconStyle(
|
|
social
|
|
)} text-decoration: none;">
|
|
${getSocialIcon(social.networkType)}
|
|
</a>`
|
|
)
|
|
.join("")}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>`;
|
|
};
|
|
|
|
// Helper functions for button alignment (same as in React components)
|
|
const getButtonHorizontalAlign = (button?: {
|
|
alignment?: HorizontalAlignment;
|
|
}) => {
|
|
if (!button || !button.alignment) return "center";
|
|
switch (button.alignment) {
|
|
case HorizontalAlignment.LEFT:
|
|
return "left";
|
|
case HorizontalAlignment.RIGHT:
|
|
return "right";
|
|
case HorizontalAlignment.CENTER:
|
|
default:
|
|
return "center";
|
|
}
|
|
};
|
|
|
|
const getButtonVerticalAlign = (button?: {
|
|
verticalAlignment?: VerticalAlignment;
|
|
}) => {
|
|
if (!button || !button.verticalAlignment) return "middle";
|
|
switch (button.verticalAlignment) {
|
|
case VerticalAlignment.TOP:
|
|
return "top";
|
|
case VerticalAlignment.BOTTOM:
|
|
return "bottom";
|
|
case VerticalAlignment.MIDDLE:
|
|
default:
|
|
return "middle";
|
|
}
|
|
};
|
|
|
|
// Render section equivalent (HeaderSection, ContentSection, FooterSection)
|
|
const renderSection = (
|
|
section: SectionType,
|
|
sectionHeight: string,
|
|
paddingTop = ""
|
|
) => {
|
|
console.log(`🔥 [ExportHTML] Rendering section:`, {
|
|
columnsCount: section.columnsCount,
|
|
itemsLength: section.items?.length || 0,
|
|
sectionHeight,
|
|
items: section.items,
|
|
});
|
|
|
|
if (section.columnsCount > 0) {
|
|
return `<tr>
|
|
${paddingTop ? `<td style="padding-top: ${paddingTop};">` : "<td>"}
|
|
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
<tbody>
|
|
<tr>
|
|
<td style="padding: 0px; border-radius: 24px;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="table-layout: fixed;">
|
|
<tbody>
|
|
<tr>
|
|
${Array.from(
|
|
{ length: section.columnsCount },
|
|
(_, index) => {
|
|
const item = section.items[index];
|
|
const hasButtons =
|
|
item?.buttons && item.buttons.length > 0;
|
|
const hasTexts =
|
|
item?.texts && item.texts.length > 0;
|
|
const hasImages =
|
|
item?.images && item.images.length > 0;
|
|
const hasSocials =
|
|
item?.socials && item.socials.length > 0;
|
|
|
|
console.log(
|
|
`🔥 [ExportHTML] Item ${index} content check:`,
|
|
{
|
|
hasButtons,
|
|
hasTexts,
|
|
hasImages,
|
|
hasSocials,
|
|
sectionHeight,
|
|
item: item,
|
|
}
|
|
);
|
|
|
|
let result = `<td style="width: ${
|
|
100 / section.columnsCount
|
|
}%; height: ${sectionHeight}; background-color: ${
|
|
item?.backgroundColor || "#ffffff"
|
|
}; ${
|
|
item?.backgroundImage
|
|
? `background-image: url(${
|
|
item.backgroundImage
|
|
}); background-size: ${
|
|
item?.style?.backgroundSize || "cover"
|
|
}; background-repeat: ${
|
|
item?.style?.backgroundRepeat || "no-repeat"
|
|
}; background-position: ${
|
|
item?.style?.backgroundPosition || "center"
|
|
};`
|
|
: ""
|
|
} padding: 0; vertical-align: top; border-radius: 8px; position: relative;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse; height: ${sectionHeight};">
|
|
<tbody>`;
|
|
|
|
// اگر محتوایی وجود دارد (متن، دکمه، تصویر، یا شبکه اجتماعی)
|
|
if (
|
|
hasTexts ||
|
|
hasButtons ||
|
|
hasImages ||
|
|
hasSocials
|
|
) {
|
|
// اگر فقط دکمه داریم و متن نداریم
|
|
if (!hasTexts && hasButtons) {
|
|
result += `<tr>
|
|
<td width="100%" height="${sectionHeight}" align="${getButtonHorizontalAlign(
|
|
item.buttons[0]
|
|
)}" valign="${getButtonVerticalAlign(
|
|
item.buttons[0]
|
|
)}" style="padding: 8px;">
|
|
${renderImages(item?.images || [])}
|
|
${renderSocials(item?.socials || [])}
|
|
${
|
|
sectionHeight === "246px"
|
|
? '<div style="margin: 0 0 10px 0; text-align: right; color: #888; font-size: 14px;">{{content}}</div>'
|
|
: ""
|
|
}
|
|
${renderButtons(item.buttons)}
|
|
</td>
|
|
</tr>`;
|
|
} else {
|
|
// ردیف اصلی برای متن و تصاویر
|
|
const mainHeight = hasButtons
|
|
? sectionHeight === "123px"
|
|
? "60"
|
|
: sectionHeight === "246px"
|
|
? "203"
|
|
: "40"
|
|
: sectionHeight === "123px"
|
|
? "87"
|
|
: sectionHeight === "246px"
|
|
? "230"
|
|
: "67";
|
|
|
|
result += `<tr>
|
|
<td width="100%" height="${mainHeight}" style="padding: 20px; vertical-align: top; font-size: 15px; line-height: 1.8;">
|
|
${renderTexts(item?.texts || [])}
|
|
${renderImages(item?.images || [])}
|
|
${renderSocials(item?.socials || [])}
|
|
${
|
|
sectionHeight === "246px" &&
|
|
!hasTexts &&
|
|
!hasImages &&
|
|
!hasSocials
|
|
? '<div style="margin: 0; text-align: right; color: #888; font-size: 14px;">{{content}}</div>'
|
|
: ""
|
|
}
|
|
</td>
|
|
</tr>`;
|
|
|
|
// ردیف دکمهها
|
|
if (hasButtons) {
|
|
result += `<tr>
|
|
<td width="100%" height="27" align="${getButtonHorizontalAlign(
|
|
item.buttons[0]
|
|
)}" valign="${getButtonVerticalAlign(
|
|
item.buttons[0]
|
|
)}" style="padding: 0 8px 8px 8px;">
|
|
${renderButtons(item.buttons)}
|
|
</td>
|
|
</tr>`;
|
|
}
|
|
}
|
|
} else {
|
|
// اگر هیچ محتوایی نداریم و content section است
|
|
console.log(
|
|
"🔥 [ExportHTML] Empty content section detected, adding {{content}} placeholder"
|
|
);
|
|
if (sectionHeight === "246px") {
|
|
result += `<tr>
|
|
<td width="100%" height="230" style="padding: 20px; vertical-align: top; font-size: 15px; line-height: 1.8;">
|
|
<div style="margin: 0; text-align: right; color: #888; font-size: 14px;">{{content}}</div>
|
|
</td>
|
|
</tr>`;
|
|
}
|
|
}
|
|
|
|
result += `</tbody>
|
|
</table>
|
|
</td>`;
|
|
|
|
// فاصله بین ستونها
|
|
if (index < section.columnsCount - 1) {
|
|
result += `<td style="width: 16px;"></td>`;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
).join("")}
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>`;
|
|
} else {
|
|
// حالت خالی
|
|
const emptyMessages = {
|
|
"123px": "سربرگ ایمیل",
|
|
"246px": "محتوای ایمیل",
|
|
};
|
|
const emptyMessage =
|
|
emptyMessages[sectionHeight as keyof typeof emptyMessages] ||
|
|
"فوتر ایمیل";
|
|
|
|
return `<tr>
|
|
${paddingTop ? `<td style="padding-top: ${paddingTop};">` : "<td>"}
|
|
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
<tbody>
|
|
<tr>
|
|
<td style="height: ${
|
|
sectionHeight === "246px" ? "286px" : sectionHeight
|
|
}; border-radius: 24px; text-align: right; vertical-align: middle; padding: 0px;">
|
|
<div style="color: #888; font-size: 14px; margin-bottom: 8px;">
|
|
${emptyMessage}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>`;
|
|
}
|
|
};
|
|
|
|
// Generate complete HTML exactly like Personality.tsx structure
|
|
const html = `<div style="max-width: 600px; margin: 0 auto; direction: rtl;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse;">
|
|
<tbody>
|
|
${renderSection(data.header, "123px")}
|
|
${renderSection(data.content, "246px", "")}
|
|
${renderSection(data.footer, "123px", "")}
|
|
</tbody>
|
|
</table>
|
|
</div>`;
|
|
|
|
console.log("🔥 [ExportHTML] Final HTML generated:", html);
|
|
return html;
|
|
};
|
|
|
|
export const copyHTMLToClipboard = async (html: string): Promise<boolean> => {
|
|
try {
|
|
await navigator.clipboard.writeText(html);
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Failed to copy HTML to clipboard:", error);
|
|
return false;
|
|
}
|
|
};
|