/**
* Style Generator Utility
* Converts block and slide style objects to CSS strings
*/
/**
* Apply opacity to a color value
* @param {string} color - CSS color (hex, rgb, etc.)
* @param {string} opacity - Opacity value 0-1
* @returns {string} Color with opacity applied
*/
function applyColorOpacity(color, opacity) {
if (!color || !opacity || opacity === '1') return color;
const op = parseFloat(opacity);
if (isNaN(op)) return color;
// If it's a hex color, convert to rgba
if (color.startsWith('#')) {
const hex = color.slice(1);
let r, g, b;
if (hex.length === 3) {
r = parseInt(hex[0] + hex[0], 16);
g = parseInt(hex[1] + hex[1], 16);
b = parseInt(hex[2] + hex[2], 16);
} else {
r = parseInt(hex.slice(0, 2), 16);
g = parseInt(hex.slice(2, 4), 16);
b = parseInt(hex.slice(4, 6), 16);
}
return `rgba(${r}, ${g}, ${b}, ${op})`;
}
// If it's already rgb, convert to rgba
if (color.startsWith('rgb(')) {
return color.replace('rgb(', 'rgba(').replace(')', `, ${op})`);
}
// If it's already rgba, replace the opacity
if (color.startsWith('rgba(')) {
return color.replace(/,\s*[\d.]+\)$/, `, ${op})`);
}
return color;
}
/**
* Generate CSS styles for a block
* @param {Object} block - Block object with style properties
* @returns {string} CSS style string
*/
function generateBlockStyles(block) {
if (!block) return '';
const styles = [];
// Text styling
if (block.textAlign) {
styles.push(`text-align: ${block.textAlign}`);
}
if (block.textColor) {
styles.push(`color: ${block.textColor}`);
}
if (block.fontSize) {
styles.push(`font-size: ${block.fontSize}`);
}
// Box model styling
if (block.boxStyle) {
const bs = block.boxStyle;
// Dimensions
if (bs.width) {
styles.push(`width: ${bs.width}`);
}
if (bs.height) {
styles.push(`height: ${bs.height}`);
}
// Spacing
if (bs.padding) {
styles.push(`padding: ${bs.padding}`);
}
if (bs.margin) {
styles.push(`margin: ${bs.margin}`);
}
// Background
if (bs.backgroundColor) {
const bgColor = applyColorOpacity(bs.backgroundColor, bs.backgroundOpacity);
styles.push(`background-color: ${bgColor}`);
}
// Border
if (bs.borderWidth || bs.borderStyle || bs.borderColor) {
const width = bs.borderWidth || '1px';
const style = bs.borderStyle || 'solid';
const color = bs.borderColor || 'currentColor';
styles.push(`border: ${width} ${style} ${color}`);
}
if (bs.borderRadius) {
styles.push(`border-radius: ${bs.borderRadius}`);
}
}
return styles.join('; ');
}
/**
* Generate CSS styles for a slide background
* @param {Object} slide - Slide object with background property
* @returns {string} CSS style string for the slide container
*/
function generateSlideBackgroundStyles(slide) {
if (!slide || !slide.background) return '';
const bg = slide.background;
const styles = [];
switch (bg.type) {
case 'color':
if (bg.color) {
const bgColor = applyColorOpacity(bg.color, bg.opacity);
styles.push(`background-color: ${bgColor}`);
}
break;
case 'image':
if (bg.imageURL) {
styles.push(`background-image: url('${bg.imageURL}')`);
styles.push(`background-size: ${bg.position || 'cover'}`);
styles.push(`background-position: center`);
styles.push(`background-repeat: no-repeat`);
// If there's an opacity overlay, we'll need to handle it differently
// using a pseudo-element or overlay div
}
break;
}
return styles.join('; ');
}
/**
* Sanitize rich text HTML content
* Only allow safe formatting tags: b, i, u, span (with color style only)
* @param {string} html - HTML content to sanitize
* @returns {string} Sanitized HTML
*/
function sanitizeRichText(html) {
if (!html) return '';
// Create a temporary container
const temp = document.createElement('div');
temp.innerHTML = html;
// Allowed tags
const allowedTags = ['B', 'I', 'U', 'SPAN', 'BR'];
// Walk the DOM and filter
function sanitizeNode(node) {
const children = Array.from(node.childNodes);
for (const child of children) {
if (child.nodeType === Node.ELEMENT_NODE) {
if (!allowedTags.includes(child.tagName)) {
// Replace disallowed element with its text content
const text = document.createTextNode(child.textContent);
node.replaceChild(text, child);
} else {
// For SPAN, only allow color style
if (child.tagName === 'SPAN') {
const color = child.style.color;
const backgroundColor = child.style.backgroundColor;
// Remove all attributes
while (child.attributes.length > 0) {
child.removeAttribute(child.attributes[0].name);
}
// Re-add only allowed styles
if (color) child.style.color = color;
if (backgroundColor) child.style.backgroundColor = backgroundColor;
} else {
// Remove all attributes from other allowed tags
while (child.attributes.length > 0) {
child.removeAttribute(child.attributes[0].name);
}
}
// Recursively sanitize children
sanitizeNode(child);
}
}
}
}
sanitizeNode(temp);
return temp.innerHTML;
}
/**
* Check if content contains any rich text formatting
* @param {string} content - Content to check
* @returns {boolean} True if content has HTML tags
*/
function hasRichTextFormatting(content) {
if (!content) return false;
return /<[^>]+>/.test(content);
}
/**
* Strip all HTML tags and return plain text
* @param {string} html - HTML content
* @returns {string} Plain text
*/
function stripHtmlTags(html) {
if (!html) return '';
const temp = document.createElement('div');
temp.innerHTML = html;
return temp.textContent || temp.innerText || '';
}
// Export for use in other scripts
window.StyleGenerator = {
generateBlockStyles,
generateSlideBackgroundStyles,
sanitizeRichText,
hasRichTextFormatting,
stripHtmlTags,
applyColorOpacity
};
Add rich text formatting and block styling to editor
Pitch decks to help promote Skyscape Apps