Developer Guide to Document Authoring with Edge Delivery Services. Part 11

Author: Tom Cranstoun

Getting AI to document your output.

Review its Code, using Cursor.

Review its Code, using EDS prompt

Review its Code, as a Senior Dev

Meta-Introspection

A Senior developer just joined my team, Claude

It's amazing what you can do with AI, properly trained, with context, operating procedures and prompts.

In my previous post I showed you Cursor/Claude creating and amending a carousel.

Not too smart you might say. Next step is to get Claude to review its code, both a self -review and as a senior developer. Now as a senior developer I also want Claude to give learning points to the team. This is called meta-introspection.

In this post, I'll walk you through how I use Cursor and Claude AI together to create components for Adobe's Document Authoring with Edge Delivery Services. This powerful combination effectively brings a senior developer to your team, making work with Adobe's Edge Delivery Services a joy.

Watch the youtube, then read the outputs below, this block is called a ‘showcaser’ and its contents are the prompts and sources

# Introduction

The Showcaser block displays a visually appealing showcase for code snippets in a book-like format, with the ability to copy code to the clipboard, expand/collapse long code snippets, and a "Return to Menu" button for easy navigation back to the top of the Showcaser.

## Behavior

- Collects all code snippets from the current page
- Creates a book-like interface with a collapsible left sidebar containing clickable titles
- Clicking a title displays the corresponding code snippet on the right page
- The first snippet is displayed by default
- For code snippets longer than 40 lines:
  - An "Expand" button appears at the top of the snippet
  - A "..." button appears at the bottom of the snippet
  - Clicking either button toggles between expanded and collapsed views
- Copy to clipboard button for each code snippet
- Syntax highlighting based on detected programming language
- Responsive layout adjusts for mobile devices
- "Return to Menu" button appears when scrolling down and, when clicked, smoothly scrolls the page back to the top of the Showcaser block
- Toggle button in the top-left corner to collapse/expand the left sidebar
# Showcaser Block Prompt
## AI Prompt, (c) Tom Cranstoun, 2024  V 1.0
You are an expert Franklin Developer
**Goal:** Create a Franklin block named showcaser that displays a visually appealing showcase for code
## Key Features:
1. Implement error handling and loading state
2. Use CSS variables for easy customization
3. Ensure accessibility and responsive design
## Requirements
The block should find all <pre> </pre> content in the current page, collect the rendered html together in an array, remove the existing <pre></pre> content, the code should add the content from all of the subsequent rows, at the start of the gathering. 
The code should create a simulated book object and place all of the pre items in by title in the left hand column and let the titles be clickable, when clicked the relevant html is placed into right hand panel, title is the first line of the gathered content
## Implementation Details:
### JavaScript:
- Display loading state and error messages
### CSS (blogroll.css):
- Use CSS variables for colors, fonts, and sizes
- Implement styles for both regular and compact variations
- Include responsive design for mobile devices
### README.md:
- Provide clear instructions for usage and authoring
- List available CSS variables for customization
### demo.md:
- Demonstrate the block's functionality
- Provide sample usage instructions
- Explain how it works
- Include metadata for the demo page
## Additional Notes:
- Ensure proper error handling and user feedback
- Optimize performance for large datasets
- Implement accessibility features using semantic HTML and ARIA attributes
- Use CSS variables consistently throughout the stylesheet
- Provide responsive design for various screen sizes
//Showcaser.js
// Constants for configuration
const SHOWCASER_CONFIG = {
  BOOK_TITLE: 'Code',
  ERROR_MESSAGE: 'Error loading content. Please try again.',
  COPY_BUTTON_RESET_DELAY: 2000,
  LONG_DOCUMENT_THRESHOLD: 40,
};

// Helper function to decode HTML entities in text
function decodeHtmlEntities(text) {
  const textArea = document.createElement('textarea');
  textArea.innerHTML = text;
  return textArea.value;
}

/**
 * Detects the programming language of the given code snippet
 * @param {string} code - The code snippet to analyze
 * @returns {string} The detected language
 */
function detectLanguage(code) {
  const decodedCode = decodeHtmlEntities(code);

  // Simple check for Markdown (including title)
  if (decodedCode.trim().startsWith('#')) {
    return 'markdown';
  }

  // Check for JSON (including title)
  const jsonTest = decodedCode.trim();
  if (jsonTest.startsWith('{') || jsonTest.includes('\n{')) {
    return 'json';
  }

  // Rest of the language detection logic (including title)
  if (decodedCode.trim().startsWith('"') || decodedCode.trim().startsWith("'")) {
    return 'text';
  }
  
  if (/^(ls|cd|pwd|mkdir|rm|cp|mv|cat|echo|grep|sed|awk|curl|wget|ssh|git|npm|yarn|docker|kubectl)\s/.test(decodedCode)) {
    return 'shell';
  }
  
  if (decodedCode.includes('function') || decodedCode.includes('var') || decodedCode.includes('const')) return 'javascript';
  if (decodedCode.includes('{') && decodedCode.includes('}')) {
    if (decodedCode.match(/[a-z-]+\s*:\s*[^;]+;/)) return 'css';
  }
  if (decodedCode.includes('<') && decodedCode.includes('>') && (decodedCode.includes('</') || decodedCode.includes('/>'))) return 'html';
  
  if (decodedCode.startsWith('$') || decodedCode.startsWith('#')) return 'shell';
  
  return 'text';
}

/**
 * Applies syntax highlighting to the given code based on the detected language
 * @param {string} code - The code to highlight
 * @param {string} language - The detected language
 * @returns {string} HTML string with syntax highlighting
 */
function highlightSyntax(code, language) {
  const encodeHtmlEntities = (text) => {
    return text
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  };

  const decodedCode = decodeHtmlEntities(code);

  switch (language) {
    case 'markdown':
      const highlightedMd = decodedCode.replace(
        /(^#{1,6}\s.*$)|(^[*-]\s.*$)|(^>\s.*$)|(`{1,3}[^`\n]+`{1,3})|(\[.*?\]\(.*?\))|(\*\*.*?\*\*)|(_.*?_)|(^```[\s\S]*?^```)/gm,
        match => {
          if (/^#{1,6}/.test(match)) return `<span class="heading">${encodeHtmlEntities(match)}</span>`;
          if (/^[*-]\s+/.test(match)) return `<span class="list-item">${encodeHtmlEntities(match)}</span>`;
          if (/^>\s+/.test(match)) return `<span class="blockquote">${encodeHtmlEntities(match)}</span>`;
          if (/`{1,3}[^`\n]+`{1,3}/.test(match)) return `<span class="inline-code">${encodeHtmlEntities(match)}</span>`;
          if (/\[.*?\]\(.*?\)/.test(match)) return `<span class="link">${encodeHtmlEntities(match)}</span>`;
          if (/\*\*.*?\*\*/.test(match)) return `<span class="bold">${encodeHtmlEntities(match)}</span>`;
          if (/_.*?_/.test(match)) return `<span class="italic">${encodeHtmlEntities(match)}</span>`;
          if (/^```[\s\S]*?^```/.test(match)) return `<span class="code-block">${encodeHtmlEntities(match)}</span>`;
          return encodeHtmlEntities(match);
        }
      );
      return highlightedMd;
    case 'javascript':
      return decodedCode.replace(
        /(\/\/.*|\/\*[\s\S]*?\*\/|'(?:\\.|[^\\'])*'|"(?:\\.|[^\\"])*"|`(?:\\.|[^\\`])*`|\b(?:function|var|let|const|if|else|for|while|do|switch|case|break|return|continue|class|new|typeof|instanceof|this|null|undefined|true|false)\b|\b\d+\b|[{}[\],;.])/g,
        match => {
          if (/^\/\//.test(match)) return `<span class="comment">${encodeHtmlEntities(match)}</span>`;
          if (/^\/\*/.test(match)) return `<span class="comment">${encodeHtmlEntities(match)}</span>`;
          if (/^['"`]/.test(match)) return `<span class="string">${encodeHtmlEntities(match)}</span>`;
          if (/^(function|var|let|const|if|else|for|while|do|switch|case|break|return|continue|class|new|typeof|instanceof|this)$/.test(match)) return `<span class="keyword">${encodeHtmlEntities(match)}</span>`;
          if (/^(null|undefined|true|false)$/.test(match)) return `<span class="boolean">${encodeHtmlEntities(match)}</span>`;
          if (/^\d+$/.test(match)) return `<span class="number">${encodeHtmlEntities(match)}</span>`;
          if (/^[{}[\],;.]$/.test(match)) return `<span class="punctuation">${encodeHtmlEntities(match)}</span>`;
          return encodeHtmlEntities(match);
        }
      );
    case 'css':
      return decodedCode.replace(
        /(\/\*[\s\S]*?\*\/)|(\b[\w-]+\s*:)|(#[\da-f]{3,6})|(\b\d+(%|px|em|rem|vh|vw)?\b)|([@.]{1}[\w-]+)/gi,
        match => {
          if (/^\/\*/.test(match)) return `<span class="comment">${encodeHtmlEntities(match)}</span>`;
          if (/:$/.test(match)) return `<span class="property">${encodeHtmlEntities(match)}</span>`;
          if (/^#/.test(match)) return `<span class="value">${encodeHtmlEntities(match)}</span>`;
          if (/^\d/.test(match)) return `<span class="number">${encodeHtmlEntities(match)}</span>`;
          if (/^[@.]/.test(match)) return `<span class="selector">${encodeHtmlEntities(match)}</span>`;
          return encodeHtmlEntities(match);
        }
      );
    case 'json':
      return decodedCode.replace(
        /(\"(?:\\.|[^\\"])*\")(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
        (match, string, colon, boolean) => {
          if (string) {
            return colon 
              ? `<span class="json-key">${encodeHtmlEntities(string)}</span>${encodeHtmlEntities(colon)}`
              : `<span class="json-string">${encodeHtmlEntities(string)}</span>`;
          }
          if (boolean) {
            return `<span class="json-boolean">${encodeHtmlEntities(boolean)}</span>`;
          }
          if (/^-?\d/.test(match)) {
            return `<span class="json-number">${encodeHtmlEntities(match)}</span>`;
          }
          return encodeHtmlEntities(match);
        }
      );
    default:
      return encodeHtmlEntities(decodedCode);
  }
}

/**
 * Main function to decorate the Showcaser block
 * @param {HTMLElement} block - The block element to decorate
 */
export default async function decorate(block) {
  // Create container and book structure
  const container = document.createElement('div');
  container.className = 'showcaser-container';
  block.appendChild(container);

  const book = document.createElement('div');
  book.className = 'showcaser-book';
  container.appendChild(book);

  const leftPage = document.createElement('div');
  leftPage.className = 'showcaser-left-page';
  leftPage.setAttribute('role', 'navigation');
  leftPage.setAttribute('aria-label', 'Code snippets navigation');
  book.appendChild(leftPage);

  // Create and set up toggle button for collapsing left page
  const toggleButton = document.createElement('button');
  toggleButton.className = 'showcaser-toggle';
  toggleButton.textContent = '<';
  toggleButton.setAttribute('aria-label', 'Toggle navigation panel');
  toggleButton.setAttribute('aria-expanded', 'true');
  book.appendChild(toggleButton);

  toggleButton.addEventListener('click', () => {
    leftPage.classList.toggle('collapsed');
    toggleButton.classList.toggle('collapsed');
    toggleButton.textContent = leftPage.classList.contains('collapsed') ? '>' : '<';
    toggleButton.setAttribute('aria-expanded', !leftPage.classList.contains('collapsed'));
  });

  const rightPage = document.createElement('div');
  rightPage.className = 'showcaser-right-page';
  rightPage.setAttribute('role', 'region');
  rightPage.setAttribute('aria-live', 'polite');
  book.appendChild(rightPage);

  const bookTitle = document.createElement('h2');
  bookTitle.textContent = SHOWCASER_CONFIG.BOOK_TITLE;
  leftPage.appendChild(bookTitle);

  // Create "Return to Menu" button and set up scroll behavior
  const returnToMenuButton = document.createElement('button');
  returnToMenuButton.className = 'showcaser-returntomenu';
  returnToMenuButton.textContent = 'Return to Menu';
  returnToMenuButton.style.display = 'none';
  container.appendChild(returnToMenuButton);

  // Show/hide "Return to Menu" button based on scroll position
  window.addEventListener('scroll', () => {
    const blockTop = block.getBoundingClientRect().top;
    const blockBottom = block.getBoundingClientRect().bottom;
    if (blockTop < 0 && blockBottom > window.innerHeight) {
      returnToMenuButton.style.display = 'block';
    } else {
      returnToMenuButton.style.display = 'none';
    }
  });

  returnToMenuButton.addEventListener('click', () => {
    block.scrollIntoView({ behavior: 'smooth' });
  });

  // Check for compact variation
  if (block.classList.contains('compact')) {
    container.classList.add('compact');
  }

  try {
    // Collect and process code snippets from the page
    const codeSnippets = [];
    const codeElements = document.querySelectorAll('pre > code');
    
    codeElements.forEach((element, index) => {
      const code = element.textContent;
      
      const lines = code.split('\n');
      const title = lines[0].replace(/\/\/|\/\*|\*\//g, '').trim() || `Code Snippet ${index + 1}`;
      
      if (code.trim()) {
        const language = detectLanguage(code);
        const highlightedCode = highlightSyntax(code, language);
        codeSnippets.push({ title, content: highlightedCode, language });
      }
    });

    // Create navigation and content for each code snippet
    codeSnippets.forEach((snippet, index) => {
      const titleElement = document.createElement('a');
      titleElement.className = 'showcaser-title';
      titleElement.textContent = snippet.title;
      titleElement.href = '#';
      titleElement.setAttribute('aria-controls', `snippet-${index}`);
      titleElement.addEventListener('click', (e) => {
        e.preventDefault();
        
        // Ensure left page is visible when a snippet is selected
        if (leftPage.classList.contains('collapsed')) {
          leftPage.classList.remove('collapsed');
          toggleButton.classList.remove('collapsed');
          rightPage.style.width = '80%';
          toggleButton.textContent = '<';
        }

        // Render the selected code snippet
        const h3 = document.createElement('h3');
        h3.id = `snippet-${index}`;
        h3.textContent = snippet.title;

        const codeWrapper = document.createElement('div');
        codeWrapper.className = 'showcaser-code-wrapper';

        const copyButton = document.createElement('button');
        copyButton.className = 'showcaser-copy';
        copyButton.textContent = `Copy ${snippet.language} to clipboard`;

        const pre = document.createElement('pre');
        pre.className = `language-${snippet.language}`;

        const code = document.createElement('code');
        code.innerHTML = snippet.content;

        pre.appendChild(code);
        codeWrapper.appendChild(copyButton);
        codeWrapper.appendChild(pre);

        rightPage.innerHTML = '';
        rightPage.appendChild(h3);
        rightPage.appendChild(codeWrapper);

        // Set up copy to clipboard functionality
        copyButton.addEventListener('click', () => {
          navigator.clipboard.writeText(code.textContent)
            .then(() => {
              copyButton.textContent = 'Copied!';
              setTimeout(() => {
                copyButton.textContent = `Copy ${snippet.language} to clipboard`;
              }, SHOWCASER_CONFIG.COPY_BUTTON_RESET_DELAY);
            })
            .catch(err => {
              console.error('Error copying content:', err);
            });
        });

        // Add expand/collapse functionality for long code snippets
        if (snippet.content.split('\n').length > SHOWCASER_CONFIG.LONG_DOCUMENT_THRESHOLD) {
          pre.classList.add('collapsible');
          
          const topExpandButton = document.createElement('button');
          topExpandButton.className = 'showcaser-expand-collapse top';
          topExpandButton.textContent = 'Expand';
          
          const bottomExpandButton = document.createElement('button');
          bottomExpandButton.className = 'showcaser-expand-collapse bottom';
          bottomExpandButton.textContent = '....';
          
          const toggleExpansion = () => {
            pre.classList.toggle('expanded');
            const isExpanded = pre.classList.contains('expanded');
            topExpandButton.textContent = isExpanded ? 'Collapse' : 'Expand';
            bottomExpandButton.textContent = isExpanded ? 'Close' : '....';
          };
          
          topExpandButton.onclick = toggleExpansion;
          bottomExpandButton.onclick = toggleExpansion;
          
          codeWrapper.insertBefore(topExpandButton, pre);
          codeWrapper.appendChild(bottomExpandButton);
        }
      });
      leftPage.appendChild(titleElement);

      // Automatically display the first snippet
      if (index === 0) {
        titleElement.click();
      }
    });

    // Remove original code elements from the page
    codeElements.forEach((element) => {
      const preElement = element.parentElement;
      if (preElement && preElement.tagName.toLowerCase() === 'pre') {
        preElement.remove();
      }
    });

  } catch (error) {
    // Handle errors and display error message
    console.error('Showcaser Error:', error);
    const errorElement = document.createElement('div');
    errorElement.className = 'showcaser-error';
    errorElement.textContent = SHOWCASER_CONFIG.ERROR_MESSAGE;
    container.appendChild(errorElement);
  }

  // Mark the block as initialized
  block.classList.add('showcaser--initialized');
}
/* showcaser.css */
/* Showcaser Block Styles */

/* Custom properties for easy theming and consistency */
.showcaser {
  /* Colors */
  --showcaser-bg-color: #f5f5f5;
  --showcaser-text-color: #333;
  --showcaser-title-color: #0066cc;
  --showcaser-border-color: #ddd;
  --showcaser-active-bg: #e6f2ff;
  
  /* Typography */
  --showcaser-font-family: Arial, sans-serif;
  --showcaser-title-font-size: 1.2rem;
  --showcaser-content-font-size: 1rem;
  
  /* Spacing */
  --showcaser-padding: 20px;
  --showcaser-border-radius: 8px;

  /* Transitions */
  --showcaser-transition-duration: 0.3s;
}

/* Main container styles */
.showcaser-container {
  background-color: var(--showcaser-bg-color);
  color: var(--showcaser-text-color);
  font-family: var(--showcaser-font-family);
  padding: var(--showcaser-padding);
  border-radius: var(--showcaser-border-radius);
}

/* Book-like layout styles */
.showcaser-book {
  display: flex;
  border: 1px solid var(--showcaser-border-color);
  border-radius: 8px;
  overflow: hidden;
  position: relative;
}

/* Common styles for left and right pages */
.showcaser-left-page,
.showcaser-right-page {
  padding: 20px;
  flex: 1;
}

/* Left page (navigation) styles */
.showcaser-left-page {
  border-right: 1px solid var(--showcaser-border-color);
  background-color: var(--showcaser-bg-color);
  font-size: 0.8rem;
  transition: flex-basis 0.3s ease, padding-top 0.3s ease;
  flex-basis: 20%;
  min-width: 150px;
  max-width: 300px;
  overflow-y: auto;
  height: 100%;
  padding-top: 40px; /* Make room for the toggle button */
}

/* Collapsed state for left page */
.showcaser-left-page.collapsed {
  flex-basis: 0;
  min-width: 0;
  border-right: none;
  padding-top: 0;
}

/* Right page (content) styles */
.showcaser-right-page {
  background-color: white;
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: auto;
  transition: flex-basis 0.3s ease;
  overflow-x: auto;
}

/* Toggle button styles */
.showcaser-toggle {
  position: absolute;
  left: 0;
  top: 0;
  background-color: var(--showcaser-title-color);
  color: white;
  border: none;
  padding: 5px 10px;
  cursor: pointer;
  z-index: 11;
  transition: background-color 0.3s ease;
}

.showcaser-toggle.collapsed {
  left: 0;
}

.showcaser-toggle:hover {
  background-color: var(--showcaser-active-bg);
  color: var(--showcaser-title-color);
}

/* Navigation title styles */
.showcaser-title {
  display: block;
  cursor: pointer;
  padding: 8px;
  margin-bottom: 4px;
  font-size: 0.9rem;
  color: var(--showcaser-title-color);
  transition: background-color 0.3s ease;
  text-decoration: none;
}

.showcaser-title:hover,
.showcaser-title.active {
  background-color: var(--showcaser-active-bg);
  text-decoration: underline;
}

/* Content title styles */
.showcaser-right-page h3 {
  margin-top: 0;
  color: var(--showcaser-title-color);
}

/* Code block styles */
.showcaser-right-page pre {
  margin-top: 30px; /* Make room for the copy button */
  font-size: var(--showcaser-content-font-size);
  white-space: pre-wrap;
  word-wrap: break-word;
  background-color: #f4f4f4;
  padding: 15px;
  border-radius: 5px;
  overflow-x: auto;
}

/* Error message styles */
.showcaser-error {
  color: red;
  text-align: center;
  padding: 20px;
}

/* Responsive design for smaller screens */
@media (max-width: 768px) {
  .showcaser-book {
    flex-direction: column;
  }

  .showcaser-left-page {
    border-right: none;
    border-bottom: 1px solid var(--showcaser-border-color);
  }
}

/* Accessibility: Focus styles for interactive elements */
.showcaser-title:focus {
  outline: 2px solid var(--showcaser-title-color);
  outline-offset: 2px;
}

/* Transition for initialization */
.showcaser:not(.showcaser--initialized) {
  opacity: 0;
}

.showcaser--initialized {
  opacity: 1;
  transition: opacity 0.5s ease;
}

/* Syntax Highlighting Styles */
.showcaser-right-page .comment { color: #008000; }
.showcaser-right-page .string { color: #a31515; }
.showcaser-right-page .keyword { color: #0000ff; }
.showcaser-right-page .boolean { color: #0000ff; }
.showcaser-right-page .number { color: #09885a; }
.showcaser-right-page .punctuation { color: #000000; }
.showcaser-right-page .property { color: #ff0000; }
.showcaser-right-page .value { color: #0451a5; }
.showcaser-right-page .selector { color: #800000; }

/* Markdown-specific styles */
.showcaser-right-page .heading { color: #800000; font-weight: bold; }
.showcaser-right-page .list-item { color: #0451a5; }
.showcaser-right-page .blockquote { color: #808080; font-style: italic; }
.showcaser-right-page .inline-code { background-color: #f0f0f0; padding: 2px 4px; border-radius: 3px; }
.showcaser-right-page .link { color: #0366d6; text-decoration: underline; }
.showcaser-right-page .bold { font-weight: bold; }
.showcaser-right-page .italic { font-style: italic; }
.showcaser-right-page .code-block { display: block; background-color: #f6f8fa; padding: 16px; margin: 0 0 16px; border-radius: 3px; }

/* JSON-specific styles */
.showcaser-right-page .json-key { color: #0451a5; }
.showcaser-right-page .json-string { color: #a31515; }
.showcaser-right-page .json-boolean { color: #0000ff; }
.showcaser-right-page .json-number { color: #098658; }

/* Copy button styles */
.showcaser-code-wrapper {
  position: relative;
  margin: 1em 0;
  padding: 30px 5px 5px; 
  border: 1px solid var(--showcaser-border-color);
  border-radius: 6px;
  overflow: hidden;
}

.showcaser-code-wrapper pre {
  margin: 0;
  padding: 10px;
  overflow-x: auto;
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
  font-size: 14px;
  line-height: 1.45;
}

/* Styles for copy and expand/collapse buttons */
.showcaser-copy,
.showcaser-expand-collapse {
  position: absolute;
  padding: 0.3em 0.6em;
  background-color: var(--showcaser-title-color);
  color: white;
  border: 1px solid var(--showcaser-border-color);
  border-radius: 3px;
  font-size: 14px;
  cursor: pointer;
  z-index: 2;
  transition: background-color 0.3s ease, box-shadow 0.3s ease;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  min-width: 70px;
  text-align: center;
}

.showcaser-copy {
  top: 5px;
  right: 5px;
}

.showcaser-expand-collapse.top {
  top: 5px;
  left: 5px;
}

.showcaser-expand-collapse.bottom {
  bottom: 5px;
  left: 5px;
  min-width: 60px;
}

.showcaser-copy:hover,
.showcaser-expand-collapse:hover {
  background-color: var(--showcaser-active-bg);
  color: var(--showcaser-title-color);
}

.showcaser-copy:focus,
.showcaser-expand-collapse:focus {
  outline: 2px solid var(--showcaser-title-color);
  outline-offset: 2px;
}

/* Styles for collapsible code blocks */
.showcaser-right-page pre.collapsible {
  max-height: 300px;
  overflow-y: hidden;
  transition: max-height 0.3s ease-out;
}

.showcaser-right-page pre.expanded {
  max-height: none;
}

/* Return to Menu button styles */
.showcaser-returntomenu {
    position: fixed;
    bottom: 20px;
    left: 20px;
    padding: 10px 20px;
    background-color: var(--showcaser-title-color);
    color: white;
    cursor: pointer;
    text-align: center;
    border-radius: 5px;
    text-decoration: none;
    font-size: 14px;
    display: none;
    border: none;
    z-index: 1000;
    transition: background-color 0.3s ease;
}

.showcaser-returntomenu:hover {
    background-color: var(--showcaser-active-bg);
    color: var(--showcaser-title-color);
}

.showcaser-returntomenu:focus {
    outline: 2px solid var(--showcaser-title-color);
    outline-offset: 2px;
}

/* Adjust padding of the code wrapper to accommodate the buttons with margin */
.showcaser-code-wrapper {
  position: relative;
  padding-top: 40px; /* Increased top padding */
  padding-bottom: 40px; /* Increased bottom padding */
  margin: 1em 5px; /* Added horizontal margin */
}

.showcaser-code-wrapper pre {
  margin-top: 5px;
  margin-bottom: 5px;
}

/* Add this to the end of the CSS file */
.showcaser.compact .showcaser-left-page {
  flex-basis: 15%;
  min-width: 120px;
}

.showcaser.compact .showcaser-right-page {
  font-size: 0.9em;
}
# Showcaser README.md

The Showcaser block displays a visually appealing showcase for code snippets in a book-like format, with the ability to copy code to the clipboard, expand/collapse long code snippets, and a "Return to Menu" button for easy navigation back to the top of the Showcaser.

## Usage

To use the Showcaser block, add the following markdown table to your Franklin document:

| Showcaser |
|-----------|

The block automatically collects all code snippets from the current page and displays them in the showcase.

## Behavior

- Collects all code snippets from the current page
- Creates a book-like interface with a collapsible left sidebar containing clickable titles
- Clicking a title displays the corresponding code snippet on the right page
- The first snippet is displayed by default
- For code snippets longer than 40 lines:
  - An "Expand" button appears at the top of the snippet
  - A "..." button appears at the bottom of the snippet
  - Clicking either button toggles between expanded and collapsed views
- Copy to clipboard button for each code snippet
- Syntax highlighting based on detected language
- Responsive layout adjusts for mobile devices
- "Return to Menu" button appears when scrolling down and, when clicked, smoothly scrolls the page back to the top of the Showcaser block
- Toggle button in the top-left corner to collapse/expand the left sidebar

## Accessibility

- Uses semantic HTML structure for better screen reader compatibility
- Clickable titles have appropriate cursor styles and hover effects
- Maintains color contrast ratios for readability
- "Return to Menu" button provides an easy way for users to navigate back to the top of the Showcaser, improving usability for all users
- Expand/collapse buttons for long code snippets improve readability and navigation
- Focus styles for interactive elements to aid keyboard navigation

## Language Detection

The Showcaser automatically detects the following languages:

- JavaScript
- CSS
- HTML
- Markdown
- Shell/Terminal commands
- JSON
- Plain text

The language detection helps in applying appropriate syntax highlighting and labeling the copy button correctly.

## Performance Considerations

- The block uses lazy initialization, only becoming fully visible when loaded
- Long code snippets are initially collapsed to improve page load times and readability
- Syntax highlighting is performed client-side to reduce server load
- The collapsible left sidebar improves usability on smaller screens

## Browser Compatibility

The Showcaser block is designed to work on modern browsers, including:

- Chrome (latest versions)
- Firefox (latest versions)
- Safari (latest versions)
- Edge (latest versions)

For older browsers, some features may have limited functionality, but the core content display should remain accessible.- Syntax highlighting based on detected language
- Responsive layout adjusts for mobile devices
- "Return to Menu" button appears when scrolling down and, when clicked, smoothly scrolls the page back to the top of the Showcaser block

## Accessibility

- Uses semantic HTML structure for better screen reader compatibility
- Clickable titles have appropriate cursor styles and hover effects
- Maintains color contrast ratios for readability
- "Return to Menu" button provides an easy way for users to navigate back to the top of the Showcaser, improving usability for all users
- Expand/collapse buttons for long code snippets improve readability and navigation
- Focus styles for interactive elements to aid keyboard navigation
## Language Detection

The Showcaser automatically detects the following languages:

- JavaScript
- CSS
- HTML
- Markdown
- Shell/Terminal commands
- JSON
- Plain text

The language detection helps in applying appropriate syntax highlighting and labeling the copy button correctly.

## Performance Considerations

- The block uses lazy initialization, only becoming fully visible when loaded
- Long code snippets are initially collapsed to improve page load times and readability
- Syntax highlighting is performed client-side to reduce server load
# Showcaser Block Self-Review

## Improvements Made
1. Organized configuration variables into a SHOWCASER_CONFIG object for better maintainability.
2. Improved accessibility by adding ARIA attributes to key elements.
3. Added support for a compact variation of the Showcaser block.
4. Reorganized CSS variables for better clarity and grouping.

## Potential Future Improvements
1. Implement keyboard navigation for better accessibility.
2. Add more language-specific syntax highlighting options.
3. Consider adding a search functionality for large code showcases.
4. Implement lazy loading for long code snippets to improve performance.

## Performance Considerations
- The block uses lazy initialization, becoming fully visible only when loaded.
- Long code snippets are initially collapsed to improve page load times and readability.
- Syntax highlighting is performed client-side to reduce server load.

## Accessibility
- Added ARIA attributes to improve screen reader compatibility.
- Implemented proper focus management for interactive elements.
- Ensured color contrast ratios meet accessibility standards.

## Browser Compatibility
The Showcaser block has been tested and works well on modern browsers (Chrome, Firefox, Safari, and Edge). Some features may have limited functionality on older browsers, but core content display remains accessible.
# Senior Developer Code Review: Showcaser Block

## Overall Summary
The Showcaser block is a well-structured and feature-rich component for displaying code snippets. It demonstrates good use of modern JavaScript features and CSS techniques. The code is generally clean and readable, with some areas for potential improvement in terms of performance optimization and enhanced accessibility.

## Major Strengths
1. Modular structure with clear separation of concerns
2. Good use of CSS variables for theming and consistency
3. Implementation of accessibility features, including ARIA attributes

## Areas for Improvement
1. Performance optimization for large code snippets
2. Enhanced error handling and user feedback
3. Improved code reusability and DRY principle application
4. Further accessibility enhancements

## Detailed Recommendations

### 1. Performance Optimization
The current implementation loads and processes all code snippets at once, which could lead to performance issues with a large number of snippets. Consider implementing lazy loading for code snippets:

`function lazyLoadSnippet(snippet, index) {
  // Load and process snippet only when needed
  // This is a simplified example
  return () => {
    const highlightedCode = highlightSyntax(snippet.code, snippet.language);
    renderSnippet(highlightedCode, index);
  };
}`

### 2. Enhanced Error Handling
While there is basic error handling, it could be more informative and user-friendly:

`try {
  // Existing code
} catch (error) {
  console.error('Showcaser Error:', error);
  const errorMessage = SHOWCASER_CONFIG.DEBUG_MODE 
    ? `Error: ${error.message}` 
    : SHOWCASER_CONFIG.ERROR_MESSAGE;
  displayErrorMessage(errorMessage);
}`

### 3. Improved Code Reusability
Some functions, like button creation, are repeated. Consider creating utility functions:

`function createButton(text, className, onClick) {
  const button = document.createElement('button');
  button.textContent = text;
  button.className = className;
  button.addEventListener('click', onClick);
  return button;
}`

### 4. Accessibility Enhancements
While ARIA attributes have been added, keyboard navigation could be improved:

`function setupKeyboardNavigation() {
  const snippetLinks = document.querySelectorAll('.showcaser-title');
  snippetLinks.forEach((link, index) => {
    link.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
        e.preventDefault();
        const nextIndex = e.key === 'ArrowDown' ? (index + 1) % snippetLinks.length : (index - 1 + snippetLinks.length) % snippetLinks.length;
        snippetLinks[nextIndex].focus();
      }
    });
  });
}`

## Code-Specific Comments

1. detectLanguage function:
   - Consider using a more robust library for language detection, as the current implementation might have limitations with complex code snippets.

2. highlightSyntax function:
   - The switch statement is quite long. Consider breaking it down into separate functions for each language to improve readability.

3. decorate function:
   - This function is quite long and handles multiple responsibilities. Consider breaking it down into smaller, more focused functions.

## Positive Highlights
1. Good use of modern JavaScript features like async/await and template literals.
2. Well-structured CSS with logical organization and use of CSS variables.
3. Attention to accessibility with ARIA attributes and focus management.

## Learning Resources
1. "Clean Code" by Robert C. Martin for principles of writing maintainable code.
2. MDN Web Docs on Web Accessibility for further accessibility improvements.
3. "JavaScript: The Good Parts" by Douglas Crockford for advanced JavaScript techniques.

## Follow-up Questions
1. What was the reasoning behind implementing custom syntax highlighting instead of using an existing library?
2. Are there plans to support additional programming languages in the future?
3. How do you envision handling very large code snippets or a large number of snippets in terms of performance?

Remember, the goal is to create maintainable, efficient, and accessible code. Keep up the good work, and don't hesitate to ask if you have any questions about these recommendations.

Setting Up Cursor

Cursor (https://www.trycursor.com/) is a clone of Microsoft Visual Studio Code, enhanced with AI capabilities. Here's how to configure it:

  1. Create an account: Cursor requires a $20/month subscription, similar to Claude AI.
  2. Configure Cursor settings:
    • Enable AI features in the Cursor Settings panel.
    • Set up simple rules that will always be sent to the AI (e.g., using async/await in Node.js, following Airbnb style guide).
    • Include a .cursorrules file for project-specific standard operating procedures.
    • Choose your preferred AI model (I use Claude 3.5 Sonnet).
    • Enable features like Cursor Tab, Partial Accepts, Cursor Predictions, and Composer.
    • Add relevant documentation (I use "EDS notes" for Edge Delivery Services).
    • Enable notepads for sharing instruction fragments across projects.

Working with AI in Cursor

Cursor provides three main ways to interact with AI:

  1. Chat: Ask questions about your codebase or get explanations of specific files.
  2. Command: Use custom commands to trigger specific AI actions (e.g., review, senior developer perspective).
  3. Composer: Create new files and components from scratch using AI prompts.

Creating a Component: The Showcaser Block

I'll demonstrate the process of creating a "Showcaser" block for displaying code snippets:

The cmd-xxxxx.md files are just prompts, telling claude how to behave

  1. Write an AI prompt: Create a markdown file with detailed instructions for the AI.
  2. Generate initial files: Use the Composer to create JavaScript, CSS, README, and demo files.
  3. Review and refine: I used the cmd-review.md to have the AI fix common mistakes and improve the code.
  4. Iterate and debug: Continuously provide feedback and ask for specific changes (e.g., adding a collapse function).
  5. Get a senior perspective: Use cmd-senior.md to get a code review and learning pointers from a senior developer perspective.
  6. Refine the prompt: Use cmd-engineer.md to have the AI suggest improvements to your initial prompt based on the final result.

The Final Result

After about two hours of iterating with the AI, I created a Showcaser component with the following features:

Conclusion

Using Cursor and Claude AI together with Adobe's Edge Delivery Services allowed me to create a complex component without writing a single line of code manually. This approach significantly speeds up development and brings senior-level expertise to your projects.

By leveraging AI in this way, you can focus on design and functionality while the AI handles the implementation details. It's a powerful workflow that can dramatically improve your productivity and code quality.

The journey into AI-powered development is just beginning, and the possibilities are limitless. Are you ready to be part of this exciting future?

For more information, resources, and updates on AI-powered development, visit https://allabout.network/blogs/ddt/

<hr>

Interested in AI consultancy, tailor-made AI training files, mentoring, or audits? Contact us to explore how we can help you leverage AI in your development processes.

Thank you for joining me in this exploration of AI-assisted block creation for Adobe's Edge Delivery Services. Stay tuned for more insights and demonstrations in future posts!

About the Author: Tom Cranstoun is a seasoned AEM expert with over 12 years of experience, including roles as a Global Architecture Director and solution architect for major projects like Nissan and Renault's Helios initiative. Now running his own consultancy, Tom continues to work with top brands and agencies, bringing his expertise in AEM and emerging technologies to the forefront of digital experiences.

Sample Training documents are available at my original experiment:

https://allabout.network/blogs/ddt/ai-generated-code

Ready to see what AI can do for your development process? Let's explore the possibilities together. Get in touch: tom@digitaldomaintechnologies.com

Thanks for reading.

Digital Domain Technologies provides expert Adobe Experience Manager (AEM) consultancy. We have collaborated with some of the world’s leading brands across various AEM platforms, including AEM Cloud, on-premise solutions, Adobe Managed Services, and Edge Delivery Services. Our portfolio includes partnerships with prominent companies such as Twitter (now X), EE, Nissan/Renault Alliance Ford, Jaguar Land Rover, McLaren Sports Cars, Hyundai Genesis, and many others.

/fragments/ddt/proposition

Related Articles

guide
Back to Top