style-generator.js
js
/**
 * 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
};
72f0edf

Add rich text formatting and block styling to editor

Connor McCutcheon
@connor
0 stars

Pitch decks to help promote Skyscape Apps

Sign in to comment Sign In