| class CodeSnippet extends HTMLElement { |
| constructor() { |
| super(); |
| this.attachShadow({ mode: 'open' }); |
| } |
|
|
| static get observedAttributes() { |
| return ['language', 'filename', 'code']; |
| } |
|
|
| connectedCallback() { |
| this.render(); |
| } |
|
|
| highlightSyntax(code) { |
| |
| return code |
| .replace(/&/g, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/(".*?")/g, '<span class="syntax-string">$1</span>') |
| .replace(/('.*?')/g, '<span class="syntax-string">$1</span>') |
| .replace(/\b(def|class|import|from|if|else|elif|for|while|return|yield|try|except|finally|with|as|pass|break|continue|lambda|None|True|False)\b/g, '<span class="syntax-keyword">$1</span>') |
| .replace(/\b(\d+\.?\d*)\b/g, '<span class="syntax-number">$1</span>') |
| .replace(/(#.*$)/gm, '<span class="syntax-comment">$1</span>') |
| .replace(/\b([A-Z][a-zA-Z0-9_]*)\b/g, '<span class="syntax-class">$1</span>') |
| .replace(/\b([a-z_][a-zA-Z0-9_]*)(?=\()/g, '<span class="syntax-function">$1</span>'); |
| } |
|
|
| render() { |
| const language = this.getAttribute('language') || 'python'; |
| const filename = this.getAttribute('filename') || 'code.py'; |
| const rawCode = this.getAttribute('code') || ''; |
| const highlightedCode = this.highlightSyntax(rawCode); |
|
|
| this.shadowRoot.innerHTML = ` |
| <style> |
| :host { |
| display: block; |
| margin: 1rem 0; |
| } |
| .code-container { |
| background: #1e1e1e; |
| border: 1px solid #2d2d2d; |
| border-radius: 0.75rem; |
| overflow: hidden; |
| font-family: 'Roboto Mono', 'Consolas', 'Monaco', monospace; |
| } |
| |
| .code-header { |
| background: #2d2d2d; |
| border-bottom: 1px solid #3d3d3d; |
| padding: 0.75rem 1rem; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| } |
| |
| .file-info { |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| color: #9aa0a6; |
| font-size: 0.875rem; |
| } |
| |
| .language-badge { |
| padding: 0.25rem 0.75rem; |
| background: rgba(66, 133, 244, 0.15); |
| color: #8ab4f8; |
| border-radius: 0.25rem; |
| font-size: 0.75rem; |
| font-weight: 500; |
| text-transform: uppercase; |
| font-family: 'Roboto Mono', monospace; |
| } |
| |
| .copy-btn { |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| padding: 0.5rem 1rem; |
| background: #3d3d3d; |
| border: 1px solid #4d4d4d; |
| border-radius: 0.5rem; |
| color: #e8eaed; |
| font-size: 0.875rem; |
| cursor: pointer; |
| transition: all 0.2s; |
| font-family: 'Roboto', sans-serif; |
| } |
| |
| .copy-btn:hover { |
| background: #4d4d4d; |
| border-color: #5f6368; |
| } |
| |
| .copy-btn.copied { |
| background: rgba(52, 168, 83, 0.2); |
| border-color: #34A853; |
| color: #81c995; |
| } |
| |
| .code-content { |
| padding: 1.5rem; |
| overflow-x: auto; |
| line-height: 1.6; |
| font-size: 0.875rem; |
| color: #e8eaed; |
| } |
| |
| .syntax-keyword { color: #c586c0; } |
| .syntax-string { color: #ce9178; } |
| .syntax-comment { color: #6a9955; font-style: italic; } |
| .syntax-function { color: #dcdcaa; } |
| .syntax-number { color: #b5cea8; } |
| .syntax-class { color: #4ec9b0; } |
| @media (max-width: 640px) { |
| .code-content { |
| font-size: 0.75rem; |
| padding: 1rem; |
| } |
| } |
| </style> |
| |
| <div class="code-container"> |
| <div class="code-header"> |
| <div class="file-info"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg> |
| <span>${filename}</span> |
| </div> |
| <div style="display: flex; align-items: center; gap: 0.75rem;"> |
| <span class="language-badge">${language}</span> |
| <button class="copy-btn" id="copyBtn"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> |
| <span>Copy</span> |
| `; |
| }, 2000); |
| }); |
| }); |
| } |
| } |
|
|
| customElements.define('code-snippet', CodeSnippet); |
| ="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> |
| <span>Copy</span> |
| </button> |
| </div> |
| </div> |
| <pre class="code-content"><code>${highlightedCode}</code></pre> |
| </div> |
| `; |
| |
| // Copy functionality |
| const copyBtn = this.shadowRoot.getElementById('copyBtn'); |
| copyBtn.addEventListener('click', () => { |
| navigator.clipboard.writeText(rawCode).then(() => { |
| copyBtn.classList.add('copied'); |
| copyBtn.innerHTML = ` |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg> |
| <span>Copied!</span> |
| `; |
| |
| setTimeout(() => { |
| copyBtn.classList.remove('copied'); |
| copyBtn.innerHTML = ` |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d |