@mindfiredigital/page-builder
Version:
418 lines (408 loc) • 11.9 kB
JavaScript
import { Canvas } from '../canvas/Canvas.js';
export class HTMLGenerator {
constructor(canvas) {
this.canvas = canvas;
this.styleElement = document.createElement('style');
document.head.appendChild(this.styleElement);
}
generateHTML() {
const canvasElement = document.getElementById('canvas');
if (!canvasElement) {
console.warn('Canvas element not found!');
return this.getBaseHTML();
}
const cleanCanvas = canvasElement.cloneNode(true);
this.cleanupElements(cleanCanvas);
return this.getBaseHTML(cleanCanvas.innerHTML);
}
getBaseHTML(bodyContent = 'children') {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Page Builder</title>
<style>
${this.generateCSS()}
</style>
</head>
<body>
<div id="canvas" class=${Canvas.layoutMode === 'grid' ? 'grid-layout-active' : 'home'}>
${bodyContent}
</div>
</body>
</html>`;
}
cleanupElements(element) {
const attributesToRemove = ['contenteditable', 'draggable'];
const classesToRemove = [
'component-controls',
'delete-icon',
'component-label',
'column-label',
'resizers',
'resizer',
'upload-btn',
'component-resizer',
'drop-preview',
'edit-link-form',
'edit-link',
];
Array.from(element.children).forEach(child => {
const childElement = child;
attributesToRemove.forEach(attr => {
childElement.removeAttribute(attr);
});
const elementsToRemove = childElement.querySelectorAll(
'.component-controls, .delete-icon, .component-label, .column-label, .resizers, .resizer, .drop-preview, .upload-btn, .edit-link, .edit-link-form, input,.cell-controls,.add-row-button,.add-multiple-rows-button,.table-btn-container, .drop-preview.visible'
);
classesToRemove.forEach(classToRemove => {
childElement.classList.remove(classToRemove);
});
elementsToRemove.forEach(el => el.remove());
if (childElement.children.length > 0) {
this.cleanupElements(childElement);
}
});
}
generateCSS() {
const canvasElement = document.getElementById('canvas');
if (!canvasElement) return '';
const backgroundColor = canvasElement
? window
.getComputedStyle(canvasElement)
.getPropertyValue('background-color')
: 'rgb(255, 255, 255)';
const styles = [];
const processedSelectors = new Set();
Canvas.layoutMode === 'grid'
? styles.push(`
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
display:flex;
overflow: hidden;
}
#canvas {
position: relative;
width: 100%;
flex-grow: 1;
min-width: 0;
background-color: ${backgroundColor};
margin: 0;
overflow: auto;
box-sizing: border-box;
}
#canvas.grid-layout-active {
display: block;
}
.container-grid-active {
display: block;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
.table-componet {
border-collapse: collapse ;
box-sizing: border-box;
}
.editable-component{
border:none !important;
box-shadow:none !important;
}
`)
: styles.push(`
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
#canvas.home {
position: relative;
display: block;
width: 100%;
min-height: 100vh;
background-color: ${backgroundColor};
margin: 0;
overflow: visible;
}
table {
border-collapse: collapse ;
}
.editable-component{
border:none !important;
box-shadow:none !important;
}
`);
const elements = canvasElement.querySelectorAll('*');
const classesToExclude = [
'component-controls',
'delete-icon',
'component-label',
'resizers',
'resizer',
'upload-btn',
'edit-link-form',
'edit-link',
];
const propertiesToExclude = [
'left',
'top',
'right',
'bottom',
'position',
'margin-left',
'margin-right',
'width',
'height',
'min-width',
'max-width',
'min-height',
'max-height',
'cursor',
'resize',
'inline-size',
'block-size',
'min-inline-size',
'min-block-size',
'max-inline-size',
'max-block-size',
];
elements.forEach((component, index) => {
// Skip excluded elements
if (classesToExclude.some(cls => component.classList.contains(cls))) {
return;
}
const computedStyles = window.getComputedStyle(component);
const componentStyles = [];
if (
component instanceof SVGElement ||
(component.closest('svg') &&
['path', 'circle', 'rect', 'polygon'].includes(
component.tagName.toLowerCase()
))
) {
this.handleSVGElement(
component,
componentStyles,
computedStyles,
index,
styles,
processedSelectors
);
return;
}
for (let i = 0; i < computedStyles.length; i++) {
const prop = computedStyles[i];
const value = computedStyles.getPropertyValue(prop);
if (Canvas.layoutMode === 'grid') {
if (prop === 'resize' || propertiesToExclude.includes(prop)) {
continue;
}
} else {
if (prop === 'resize') {
continue;
}
}
if (
value &&
value !== 'initial' &&
value !== 'auto' &&
value !== 'none' &&
value !== ''
) {
componentStyles.push(`${prop}: ${value};`);
}
}
const selector = this.generateUniqueSelector(component);
if (!processedSelectors.has(selector) && componentStyles.length > 0) {
processedSelectors.add(selector);
styles.push(`
${selector} {
${componentStyles.join('\n ')}
}`);
}
});
return styles.join('\n');
}
handleSVGElement(
component,
componentStyles,
computedStyles,
index,
styles,
processedSelectors
) {
const isSVGChild =
component.tagName.toLowerCase() === 'path' ||
component.tagName.toLowerCase() === 'circle' ||
component.tagName.toLowerCase() === 'rect' ||
component.tagName.toLowerCase() === 'polygon';
if (isSVGChild) {
const specificSelector = this.generateSVGSpecificSelector(
component,
index
);
const svgProperties = [
'fill',
'stroke',
'stroke-width',
'opacity',
'fill-opacity',
'stroke-opacity',
];
svgProperties.forEach(prop => {
const value = computedStyles.getPropertyValue(prop);
if (value && value !== 'none' && value !== '' && value !== 'initial') {
componentStyles.push(`${prop}: ${value} !important;`);
}
});
if (componentStyles.length > 0) {
styles.push(`
${specificSelector} {
${componentStyles.join('\n ')}
}`);
}
} else {
for (let i = 0; i < computedStyles.length; i++) {
const prop = computedStyles[i];
const value = computedStyles.getPropertyValue(prop);
if (prop === 'resize') continue;
if (
value &&
value !== 'initial' &&
value !== 'auto' &&
value !== 'none' &&
value !== ''
) {
componentStyles.push(`${prop}: ${value};`);
}
}
const selector = this.generateUniqueSelector(component);
if (!processedSelectors.has(selector) && componentStyles.length > 0) {
processedSelectors.add(selector);
styles.push(`
${selector} {
${componentStyles.join('\n ')}
}`);
}
}
}
generateSVGSpecificSelector(element, index) {
const parentSVG = element.closest('svg');
const parentContainer =
parentSVG === null || parentSVG === void 0
? void 0
: parentSVG.parentElement;
let selector = '';
if (parentContainer) {
if (parentContainer.id) {
selector += `#${parentContainer.id} `;
} else if (parentContainer.className) {
const cleanClasses = parentContainer.className
.toString()
.split(' ')
.filter(
cls =>
!cls.includes('component-') &&
!cls.includes('delete-') &&
!cls.includes('resizer')
)
.join('.');
if (cleanClasses) {
selector += `.${cleanClasses} `;
}
}
}
if (parentSVG) {
if (parentSVG.className.baseVal) {
selector += `svg.${parentSVG.className.baseVal.split(' ').join('.')} `;
} else {
selector += 'svg ';
}
}
const parent = element.parentElement;
if (parent) {
const siblings = Array.from(parent.children).filter(
child => child.tagName === element.tagName
);
const elementIndex = siblings.indexOf(element);
selector += `${element.tagName.toLowerCase()}:nth-of-type(${elementIndex + 1})`;
} else {
selector += `${element.tagName.toLowerCase()}`;
}
return selector || `${element.tagName.toLowerCase()}-${index}`;
}
generateUniqueSelector(element) {
// If the element has an ID, that is the most unique selector
if (element.id) {
return `#${element.id}`;
}
const selectorPath = [];
let currentElement = element;
while (currentElement && currentElement.tagName.toLowerCase() !== 'body') {
let selector = currentElement.tagName.toLowerCase();
// Add clean classes to the selector
const cleanClasses = Array.from(currentElement.classList).filter(
cls =>
![
'component-controls',
'delete-icon',
'component-label',
'column-label',
'resizers',
'resizer',
'upload-btn',
'edit-link-form',
'edit-link',
'component-resizer',
'drop-preview',
].includes(cls)
);
if (cleanClasses.length > 0) {
selector += `.${cleanClasses.join('.')}`;
}
// Add nth-of-type to differentiate siblings
const parent = currentElement.parentElement;
if (parent) {
const siblings = Array.from(parent.children).filter(
child => child.tagName === currentElement.tagName
);
if (siblings.length > 1) {
const index = siblings.indexOf(currentElement) + 1;
selector += `:nth-of-type(${index})`;
}
}
selectorPath.unshift(selector);
// Stop if we hit a parent with an ID
if (currentElement.parentElement && currentElement.parentElement.id) {
selectorPath.unshift(`#${currentElement.parentElement.id}`);
break;
}
currentElement = currentElement.parentElement;
}
// Ensure the selector starts from the canvas
const finalSelector = `#canvas > ${selectorPath.join(' > ')}`;
return finalSelector;
}
applyCSS(css) {
this.styleElement.textContent = css;
}
}