slideshare/src/utils/cssParser.ts
Michael Mainguy 8376e77df7 Refactor presentation components following coding guidelines
## Major Refactoring
- Broke down large components into focused, reusable pieces
- Reduced NewPresentationPage.tsx from 238 to 172 lines
- Reduced PresentationEditor.tsx from 457 to 261 lines
- Eliminated functions exceeding 50-line guideline

## New Reusable Components
- PresentationDetailsForm: Form inputs for title/description
- AspectRatioSelector: Aspect ratio selection grid
- ThemeSelectionSection: Theme selection wrapper
- CreationActions: Action buttons and selected theme info
- EmptyPresentationState: Empty presentation state display
- SlidesSidebar: Complete sidebar with slides list
- SlideThumbnail: Individual slide thumbnail with actions
- LoadingState: Reusable loading component with spinner
- ErrorState: Reusable error display with retry/back actions

## New Hooks
- useSlideOperations: Custom hook for slide duplicate/delete logic

## Code Quality Improvements
- Replaced browser alert() calls with AlertDialog component
- Updated imports to use direct .tsx extensions per IMPORT_STANDARDS.md
- Eliminated browser confirm() calls in favor of ConfirmDialog system
- Consolidated duplicate loading/error state patterns
- Improved type safety throughout

## Benefits
- Better maintainability through component separation
- Consistent UX with shared UI components
- Code reuse across presentation components
- Compliance with 200-line file guideline

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 06:23:45 -05:00

170 lines
4.7 KiB
TypeScript

import { loggers } from './logger';
export interface CSSVariables {
[key: string]: string;
}
export interface ThemeMetadata {
id: string;
name: string;
description: string;
author?: string;
version?: string;
}
/**
* Parses CSS variables from CSS text content using regex
*/
export const parseCSSVariables = (cssContent: string): CSSVariables => {
const variables: CSSVariables = {};
// Parse :root rules and extract CSS variables
const rootRegex = /:root\s*\{([^}]+)\}/gs;
let rootMatch;
while ((rootMatch = rootRegex.exec(cssContent)) !== null) {
const rootContent = rootMatch[1];
const variableRegex = /--([^:]+):\s*([^;]+);/g;
let variableMatch;
while ((variableMatch = variableRegex.exec(rootContent)) !== null) {
const [, name, value] = variableMatch;
variables[name.trim()] = value.trim();
}
}
return variables;
};
/**
* Loads CSS file and extracts variables
*/
export const loadCSSVariables = async (cssFilePath: string): Promise<CSSVariables> => {
try {
const response = await fetch(cssFilePath);
if (!response.ok) {
throw new Error(`Failed to load CSS file: ${cssFilePath}`);
}
const cssContent = await response.text();
return parseCSSVariables(cssContent);
} catch (error) {
loggers.theme.warn(`Could not load CSS variables from ${cssFilePath}`, error instanceof Error ? error : new Error(String(error)));
return {};
}
};
/**
* Gets computed CSS variable value from the document
*/
export const getCSSVariable = (variableName: string, element?: HTMLElement): string => {
const target = element || document.documentElement;
return getComputedStyle(target).getPropertyValue(`--${variableName}`).trim();
};
/**
* Sets CSS variable on the document or element
*/
export const setCSSVariable = (variableName: string, value: string, element?: HTMLElement): void => {
const target = element || document.documentElement;
target.style.setProperty(`--${variableName}`, value);
};
/**
* Parses theme metadata from CSS comment block using regex
*/
export const parseThemeMetadata = (cssContent: string): ThemeMetadata | null => {
const commentRegex = /\/\*\s*([\s\S]*?)\s*\*\//;
const match = commentRegex.exec(cssContent);
if (!match) return null;
const commentContent = match[1];
const metadata: Partial<ThemeMetadata> = {};
// Parse each line in the comment
const lines = commentContent.split('\n');
for (const line of lines) {
const cleanLine = line.replace(/^\s*\*\s?/, '').trim();
if (cleanLine.includes(':')) {
const [key, ...valueParts] = cleanLine.split(':');
const value = valueParts.join(':').trim();
switch (key.toLowerCase().trim()) {
case 'theme':
metadata.id = value.toLowerCase().replace(/\s+/g, '-');
break;
case 'name':
metadata.name = value;
break;
case 'description':
metadata.description = value;
break;
case 'author':
metadata.author = value;
break;
case 'version':
metadata.version = value;
break;
}
}
}
// Ensure required fields
if (!metadata.id || !metadata.name || !metadata.description) {
return null;
}
return metadata as ThemeMetadata;
};
/**
* Gets all CSS variables from computed styles
*/
export const getAllCSSVariables = (element?: HTMLElement): CSSVariables => {
const target = element || document.documentElement;
const computedStyles = getComputedStyle(target);
const variables: CSSVariables = {};
// Get all CSS properties and filter for custom properties
for (let i = 0; i < computedStyles.length; i++) {
const property = computedStyles[i];
if (property.startsWith('--')) {
const name = property.substring(2); // Remove '--' prefix
const value = computedStyles.getPropertyValue(property).trim();
if (value) {
variables[name] = value;
}
}
}
return variables;
};
/**
* Loads CSS file and extracts both metadata and variables
*/
export const loadThemeFromCSS = async (cssFilePath: string): Promise<{
metadata: ThemeMetadata | null;
variables: CSSVariables;
}> => {
try {
const response = await fetch(cssFilePath);
if (!response.ok) {
throw new Error(`Failed to load CSS file: ${cssFilePath}`);
}
const cssContent = await response.text();
return {
metadata: parseThemeMetadata(cssContent),
variables: parseCSSVariables(cssContent)
};
} catch (error) {
loggers.theme.warn(`Could not load theme from ${cssFilePath}`, error instanceof Error ? error : new Error(String(error)));
return {
metadata: null,
variables: {}
};
}
};