Download scripts/convert_slides.py from MHamdan/SPARKNET: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/spaces/MHamdan/SPARKNET/resolve/76c3b0a8cd44eb930c2ad41fc0e95acdb3cd2bab/scripts/convert_slides.py
- Command line
-
hf download hf://spaces/MHamdan/SPARKNET@76c3b0a8cd44eb930c2ad41fc0e95acdb3cd2bab/scripts/convert_slides.py
-
curl -L -o convert_slides.py https://huggingface.co/spaces/MHamdan/SPARKNET/resolve/76c3b0a8cd44eb930c2ad41fc0e95acdb3cd2bab/scripts/convert_slides.py
10.8 kB
| #!/usr/bin/env python3 | |
| """ | |
| Convert Markdown slides to HTML presentation | |
| """ | |
| import re | |
| from pathlib import Path | |
| def convert_md_to_html(md_file, output_file): | |
| """Convert Markdown to HTML presentation""" | |
| # Read markdown content | |
| with open(md_file, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| # Split by --- to get individual slides | |
| slides = content.split('\n---\n') | |
| # HTML template with reveal.js-like styling | |
| html = """<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SPARKNET Presentation</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| overflow: hidden; | |
| } | |
| .slide-container { | |
| width: 100vw; | |
| height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| } | |
| .slide { | |
| background: white; | |
| width: 90%; | |
| max-width: 1200px; | |
| height: 85vh; | |
| padding: 60px; | |
| border-radius: 20px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); | |
| overflow-y: auto; | |
| display: none; | |
| } | |
| .slide.active { | |
| display: block; | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .slide.lead { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| text-align: center; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| h1 { | |
| font-size: 3em; | |
| margin-bottom: 0.5em; | |
| color: #2c3e50; | |
| } | |
| .slide.lead h1 { | |
| color: white; | |
| font-size: 4em; | |
| } | |
| h2 { | |
| font-size: 2em; | |
| margin: 0.5em 0; | |
| color: #34495e; | |
| border-bottom: 3px solid #667eea; | |
| padding-bottom: 10px; | |
| } | |
| .slide.lead h2 { | |
| color: rgba(255,255,255,0.9); | |
| border: none; | |
| font-size: 2em; | |
| } | |
| h3 { | |
| font-size: 1.5em; | |
| margin: 1em 0 0.5em 0; | |
| color: #667eea; | |
| } | |
| h4 { | |
| font-size: 1.2em; | |
| margin: 0.8em 0 0.4em 0; | |
| color: #764ba2; | |
| } | |
| p { | |
| line-height: 1.8; | |
| margin: 0.8em 0; | |
| font-size: 1.1em; | |
| } | |
| ul, ol { | |
| margin: 1em 0 1em 2em; | |
| line-height: 2; | |
| } | |
| li { | |
| margin: 0.5em 0; | |
| font-size: 1.05em; | |
| } | |
| pre { | |
| background: #f8f9fa; | |
| padding: 20px; | |
| border-radius: 8px; | |
| overflow-x: auto; | |
| margin: 1em 0; | |
| border-left: 4px solid #667eea; | |
| } | |
| code { | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.9em; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 1.5em 0; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| th, td { | |
| padding: 15px; | |
| text-align: left; | |
| border-bottom: 1px solid #ddd; | |
| } | |
| th { | |
| background: #667eea; | |
| color: white; | |
| font-weight: 600; | |
| } | |
| tr:hover { | |
| background: #f8f9fa; | |
| } | |
| .controls { | |
| position: fixed; | |
| bottom: 30px; | |
| right: 30px; | |
| display: flex; | |
| gap: 15px; | |
| z-index: 1000; | |
| } | |
| button { | |
| background: white; | |
| border: none; | |
| padding: 15px 25px; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| font-size: 1.1em; | |
| font-weight: 600; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| transition: all 0.3s; | |
| color: #667eea; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(0,0,0,0.3); | |
| background: #667eea; | |
| color: white; | |
| } | |
| .slide-number { | |
| position: fixed; | |
| bottom: 30px; | |
| left: 30px; | |
| background: white; | |
| padding: 10px 20px; | |
| border-radius: 50px; | |
| font-weight: 600; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| color: #667eea; | |
| } | |
| strong { | |
| color: #667eea; | |
| font-weight: 700; | |
| } | |
| .slide.lead strong { | |
| color: white; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="slide-container"> | |
| """ | |
| # Process each slide | |
| slide_html = [] | |
| for i, slide in enumerate(slides): | |
| if not slide.strip(): | |
| continue | |
| # Check if it's a lead slide | |
| is_lead = '<!-- _class: lead -->' in slide | |
| # Remove marp directives | |
| slide = re.sub(r'<!--.*?-->', '', slide, flags=re.DOTALL) | |
| slide = re.sub(r'^---.*?$', '', slide, flags=re.MULTILINE) | |
| # Convert markdown to HTML (basic conversion) | |
| slide = re.sub(r'^# (.*?)$', r'<h1>\1</h1>', slide, flags=re.MULTILINE) | |
| slide = re.sub(r'^## (.*?)$', r'<h2>\1</h2>', slide, flags=re.MULTILINE) | |
| slide = re.sub(r'^### (.*?)$', r'<h3>\1</h3>', slide, flags=re.MULTILINE) | |
| slide = re.sub(r'^#### (.*?)$', r'<h4>\1</h4>', slide, flags=re.MULTILINE) | |
| # Convert bold and italic | |
| slide = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', slide) | |
| slide = re.sub(r'\*(.*?)\*', r'<em>\1</em>', slide) | |
| # Convert lists | |
| slide = re.sub(r'^- (.*?)$', r'<li>\1</li>', slide, flags=re.MULTILINE) | |
| slide = re.sub(r'(<li>.*?</li>)', r'<ul>\1</ul>', slide, flags=re.DOTALL) | |
| slide = re.sub(r'</ul>\s*<ul>', '', slide) | |
| # Convert code blocks | |
| slide = re.sub(r'```(.*?)```', r'<pre><code>\1</code></pre>', slide, flags=re.DOTALL) | |
| # Convert tables | |
| lines = slide.split('\n') | |
| in_table = False | |
| table_html = [] | |
| processed_lines = [] | |
| for line in lines: | |
| if '|' in line and not line.strip().startswith('<'): | |
| if not in_table: | |
| in_table = True | |
| table_html = ['<table>'] | |
| cells = [cell.strip() for cell in line.split('|')[1:-1]] | |
| if all(set(cell) <= {'-', ' ', ':'} for cell in cells): | |
| continue # Skip separator line | |
| if len(table_html) == 1: # First row (header) | |
| table_html.append('<thead><tr>') | |
| for cell in cells: | |
| table_html.append(f'<th>{cell}</th>') | |
| table_html.append('</tr></thead><tbody>') | |
| else: | |
| table_html.append('<tr>') | |
| for cell in cells: | |
| table_html.append(f'<td>{cell}</td>') | |
| table_html.append('</tr>') | |
| else: | |
| if in_table: | |
| table_html.append('</tbody></table>') | |
| processed_lines.append(''.join(table_html)) | |
| in_table = False | |
| table_html = [] | |
| processed_lines.append(line) | |
| if in_table: | |
| table_html.append('</tbody></table>') | |
| processed_lines.append(''.join(table_html)) | |
| slide = '\n'.join(processed_lines) | |
| # Wrap paragraphs | |
| slide = re.sub(r'^([^<\n][^\n]*?)$', r'<p>\1</p>', slide, flags=re.MULTILINE) | |
| lead_class = ' lead' if is_lead else '' | |
| active_class = ' active' if i == 0 else '' | |
| slide_html.append(f' <div class="slide{lead_class}{active_class}" data-slide="{i}">\n{slide}\n </div>') | |
| html += '\n'.join(slide_html) | |
| # Add controls and JavaScript | |
| html += """ | |
| </div> | |
| <div class="slide-number"> | |
| <span id="current-slide">1</span> / <span id="total-slides"></span> | |
| </div> | |
| <div class="controls"> | |
| <button onclick="previousSlide()">← Previous</button> | |
| <button onclick="nextSlide()">Next →</button> | |
| </div> | |
| <script> | |
| let currentSlide = 0; | |
| const slides = document.querySelectorAll('.slide'); | |
| const totalSlides = slides.length; | |
| document.getElementById('total-slides').textContent = totalSlides; | |
| function showSlide(n) { | |
| slides[currentSlide].classList.remove('active'); | |
| currentSlide = (n + totalSlides) % totalSlides; | |
| slides[currentSlide].classList.add('active'); | |
| document.getElementById('current-slide').textContent = currentSlide + 1; | |
| } | |
| function nextSlide() { | |
| showSlide(currentSlide + 1); | |
| } | |
| function previousSlide() { | |
| showSlide(currentSlide - 1); | |
| } | |
| // Keyboard navigation | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'ArrowRight' || e.key === ' ') { | |
| nextSlide(); | |
| } else if (e.key === 'ArrowLeft') { | |
| previousSlide(); | |
| } | |
| }); | |
| // Touch navigation for mobile | |
| let touchStartX = 0; | |
| let touchEndX = 0; | |
| document.addEventListener('touchstart', (e) => { | |
| touchStartX = e.changedTouches[0].screenX; | |
| }); | |
| document.addEventListener('touchend', (e) => { | |
| touchEndX = e.changedTouches[0].screenX; | |
| if (touchStartX - touchEndX > 50) { | |
| nextSlide(); | |
| } else if (touchEndX - touchStartX > 50) { | |
| previousSlide(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # Write HTML file | |
| with open(output_file, 'w', encoding='utf-8') as f: | |
| f.write(html) | |
| print(f"✓ Created HTML presentation: {output_file}") | |
| if __name__ == '__main__': | |
| md_file = Path('SPARKNET_Slides.md') | |
| html_file = Path('SPARKNET_Slides.html') | |
| if not md_file.exists(): | |
| print(f"Error: {md_file} not found") | |
| exit(1) | |
| convert_md_to_html(md_file, html_file) | |
| print(f"\nOpen {html_file} in your browser to view the presentation") | |
| print("Use arrow keys or buttons to navigate") | |