Files
dees-catalog/readme.plan.md
Juergen Kunz e9541da8ff refactor
2025-06-24 22:45:50 +00:00

8.6 KiB
Raw Permalink Blame History

WYSIWYG Editor Refactoring Plan

Current Status

The dees-wysiwyg-block.ts file has grown to over 2100 lines and contains:

  • Main component logic
  • CSS styles for all block types
  • Rendering logic for each block type
  • Setup methods for each block type
  • Helper methods for various functionality

This makes the file difficult to maintain and extend.

Refactoring Goals

  1. Modularity: Each block type should be self-contained
  2. Extensibility: Adding new block types should be straightforward
  3. Maintainability: Code should be organized by responsibility
  4. Type Safety: Strong interfaces to ensure consistent implementation
  5. Performance: Enable potential lazy loading of block types

Proposed File Structure

ts_web/elements/wysiwyg/
 dees-wysiwyg-block.ts (main component - simplified)
 blocks/
    index.ts (exports all block types)
    block.base.ts (base class/interface)
    block.registry.ts (block type registry)
    block.styles.ts (common block styles)
   
    text/
       paragraph.block.ts
       heading.block.ts
       quote.block.ts
       code.block.ts
       list.block.ts
   
    media/
       image.block.ts
       youtube.block.ts
       attachment.block.ts
   
    content/
       markdown.block.ts
       html.block.ts
       divider.block.ts
   
    utils/
        file.utils.ts
        media.utils.ts
        markdown.utils.ts

Implementation Details

1. Block Handler Interface

// block.base.ts
export interface IBlockHandler {
  type: string;
  render(block: IBlock, isSelected: boolean): string;
  setup(element: HTMLElement, block: IBlock, handlers: IBlockEventHandlers): void;
  getStyles(): string;
  getPlaceholder?(): string;
}

export interface IBlockEventHandlers {
  onInput: (e: InputEvent) => void;
  onKeyDown: (e: KeyboardEvent) => void;
  onFocus: () => void;
  onBlur: () => void;
  onCompositionStart: () => void;
  onCompositionEnd: () => void;
  onMouseUp?: (e: MouseEvent) => void;
}

export abstract class BaseBlockHandler implements IBlockHandler {
  abstract type: string;
  abstract render(block: IBlock, isSelected: boolean): string;
  
  // Default implementation for common setup
  setup(element: HTMLElement, block: IBlock, handlers: IBlockEventHandlers): void {
    // Common setup logic
  }
  
  // Common styles can be defined here
  getStyles(): string {
    return '';
  }
}

2. Block Registry Pattern

// block.registry.ts
export class BlockRegistry {
  private static handlers = new Map<string, IBlockHandler>();
  
  static register(type: string, handler: IBlockHandler): void {
    this.handlers.set(type, handler);
  }
  
  static getHandler(type: string): IBlockHandler | undefined {
    return this.handlers.get(type);
  }
  
  static getAllTypes(): string[] {
    return Array.from(this.handlers.keys());
  }
}

3. Example Block Implementation

// media/image.block.ts
export class ImageBlockHandler extends BaseBlockHandler {
  type = 'image';
  
  render(block: IBlock, isSelected: boolean): string {
    const selectedClass = isSelected ? ' selected' : '';
    const imageUrl = block.metadata?.url || '';
    const isLoading = block.metadata?.loading || false;
    
    return `
      <div class="block image${selectedClass}" data-block-id="${block.id}" data-block-type="${block.type}" tabindex="0">
        ${isLoading ? `<div class="image-loading">Uploading image...</div>` : ''}
        ${imageUrl ? this.renderImage(imageUrl, block.content) : this.renderPlaceholder()}
      </div>
    `;
  }
  
  private renderImage(url: string, alt: string): string {
    return `
      <div class="image-container">
        <img src="${url}" alt="${alt || 'Uploaded image'}" />
      </div>
    `;
  }
  
  private renderPlaceholder(): string {
    return `
      <div class="image-upload-placeholder">
        <div class="upload-icon">=<3D></div>
        <div class="upload-text">Click to upload an image</div>
        <div class="upload-hint">or drag and drop</div>
      </div>
      <input type="file" accept="image/*" style="display: none;" />
    `;
  }
  
  setup(element: HTMLElement, block: IBlock, handlers: IBlockEventHandlers): void {
    const imageBlock = element.querySelector('.block.image') as HTMLDivElement;
    if (!imageBlock) return;
    
    // Setup click handlers
    imageBlock.addEventListener('click', (e) => {
      if ((e.target as HTMLElement).tagName !== 'INPUT') {
        e.stopPropagation();
        imageBlock.focus();
        handlers.onFocus();
      }
    });
    
    // Setup file upload
    this.setupFileUpload(imageBlock, block, handlers);
    
    // Setup drag and drop
    this.setupDragDrop(imageBlock, block, handlers);
    
    // Setup keyboard events
    this.setupKeyboardEvents(imageBlock, handlers);
  }
  
  getStyles(): string {
    return `
      .block.image {
        min-height: 200px;
        padding: 0;
        margin: 16px 0;
        border-radius: 8px;
        overflow: hidden;
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        transition: all 0.15s ease;
      }
      
      .block.image:focus {
        outline: none;
      }
      
      .block.image.selected {
        box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.3);
      }
      
      /* ... rest of image-specific styles ... */
    `;
  }
}

Migration Strategy

Phase 1: Infrastructure (Week 1)

  • Create blocks/ directory structure
  • Implement IBlockHandler interface and BaseBlockHandler class
  • Implement BlockRegistry with registration mechanism
  • Create block.styles.ts for common styles
  • Set up exports in blocks/index.ts

Phase 2: Proof of Concept (Week 1)

  • Migrate divider block as the simplest example
  • Update main component to use registry for divider blocks
  • Test that divider blocks work identically to before
  • Document any issues or adjustments needed

Phase 3: Text Blocks Migration (Week 2)

  • Migrate paragraph block
  • Migrate heading blocks (h1, h2, h3)
  • Migrate quote block
  • Migrate code block
  • Migrate list block
  • Extract common text formatting utilities

Phase 4: Media Blocks Migration (Week 2)

  • Migrate image block
  • Migrate youtube block
  • Migrate attachment block
  • Extract file handling utilities to utils/file.utils.ts
  • Extract media utilities to utils/media.utils.ts

Phase 5: Content Blocks Migration (Week 3)

  • Migrate markdown block
  • Migrate html block
  • Extract markdown utilities to utils/markdown.utils.ts

Phase 6: Cleanup (Week 3)

  • Remove old code from main component
  • Optimize imports and exports
  • Update documentation
  • Performance testing
  • Bundle size analysis

Technical Considerations

1. Backwards Compatibility

  • Ensure all existing functionality remains intact
  • No breaking changes to the public API
  • Maintain existing event handling patterns

2. Performance

  • Monitor bundle size impact
  • Consider dynamic imports for heavy block types:
    // Lazy load markdown handler
    const { MarkdownBlockHandler } = await import('./content/markdown.block.js');
    

3. Type Safety

  • All block handlers must implement IBlockHandler
  • Proper typing for block metadata
  • Type guards for block-specific operations

4. Testing Strategy

  • Unit tests for each block handler
  • Integration tests for the registry
  • E2E tests to ensure no regressions

5. Style Management

  • Common styles in block.styles.ts
  • Block-specific styles returned by getStyles()
  • Theme variables properly utilized

Success Criteria

  1. Code Organization: Each file under 300 lines
  2. Single Responsibility: Each class handles one block type
  3. No Regressions: All existing functionality works identically
  4. Improved Developer Experience: Adding new blocks is straightforward
  5. Performance: No significant increase in bundle size or runtime overhead

Future Enhancements

  1. Plugin System: Allow external block types to be registered
  2. Lazy Loading: Dynamic imports for rarely used block types
  3. Block Templates: Predefined configurations for common use cases
  4. Block Transformations: Convert between compatible block types
  5. Custom Block API: Allow users to define their own block types

Notes

  • This refactoring should be done incrementally to minimize risk
  • Each phase should be fully tested before moving to the next
  • Regular code reviews to ensure consistency
  • Documentation should be updated as we go