Download index.html from jsfs11/md-reader: direct link, hf CLI and curl.
- Browser
- Download file 27.1 kB
-
https://huggingface.co/spaces/jsfs11/md-reader/resolve/main/index.html
- Command line
-
hf download hf://spaces/jsfs11/md-reader/index.html
-
curl -L -o index.html https://huggingface.co/spaces/jsfs11/md-reader/resolve/main/index.html
27.1 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Markdown Story Viewer</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .markdown-body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| line-height: 1.6; | |
| color: #374151; | |
| width: 100%; | |
| max-width: 100%; | |
| overflow-wrap: break-word; | |
| word-wrap: break-word; | |
| hyphens: auto; | |
| } | |
| .markdown-body h1 { | |
| font-size: 2em; | |
| border-bottom: 1px solid #e5e7eb; | |
| padding-bottom: 0.3em; | |
| margin-top: 1em; | |
| margin-bottom: 0.6em; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body h2 { | |
| font-size: 1.5em; | |
| border-bottom: 1px solid #e5e7eb; | |
| padding-bottom: 0.3em; | |
| margin-top: 1em; | |
| margin-bottom: 0.6em; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body h3 { | |
| font-size: 1.25em; | |
| margin-top: 1em; | |
| margin-bottom: 0.6em; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body p { | |
| margin-top: 0.8em; | |
| margin-bottom: 0.8em; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body a { | |
| color: #3b82f6; | |
| text-decoration: none; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body a:hover { | |
| text-decoration: underline; | |
| } | |
| .markdown-body blockquote { | |
| border-left: 4px solid #e5e7eb; | |
| padding-left: 1em; | |
| color: #6b7280; | |
| margin-left: 0; | |
| margin-right: 0; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body code { | |
| font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; | |
| background-color: rgba(175, 184, 193, 0.2); | |
| border-radius: 6px; | |
| padding: 0.2em 0.4em; | |
| font-size: 85%; | |
| overflow-wrap: break-word; | |
| white-space: pre-wrap; | |
| } | |
| .markdown-body pre { | |
| background-color: #f9fafb; | |
| border-radius: 6px; | |
| padding: 1em; | |
| overflow: auto; | |
| line-height: 1.45; | |
| max-width: 100%; | |
| } | |
| .markdown-body pre code { | |
| background-color: transparent; | |
| padding: 0; | |
| border-radius: 0; | |
| white-space: pre; | |
| overflow-x: auto; | |
| display: block; | |
| } | |
| .markdown-body img { | |
| max-width: 100%; | |
| height: auto; | |
| border-radius: 6px; | |
| margin: 1em 0; | |
| } | |
| .markdown-body ul, .markdown-body ol { | |
| padding-left: 2em; | |
| margin-top: 0.8em; | |
| margin-bottom: 0.8em; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body li { | |
| margin-bottom: 0.4em; | |
| overflow-wrap: break-word; | |
| } | |
| .markdown-body table { | |
| border-collapse: collapse; | |
| width: 100%; | |
| margin: 1em 0; | |
| display: block; | |
| overflow-x: auto; | |
| white-space: nowrap; | |
| } | |
| .markdown-body th, .markdown-body td { | |
| border: 1px solid #e5e7eb; | |
| padding: 0.5em 1em; | |
| } | |
| .markdown-body th { | |
| background-color: #f9fafb; | |
| font-weight: 600; | |
| } | |
| .markdown-body hr { | |
| border: none; | |
| border-top: 1px solid #e5e7eb; | |
| margin: 1.5em 0; | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .sidebar-item:hover { | |
| background-color: rgba(59, 130, 246, 0.1); | |
| } | |
| .sidebar-item.active { | |
| background-color: rgba(59, 130, 246, 0.2); | |
| border-left: 3px solid #3b82f6; | |
| } | |
| .content-container { | |
| scroll-behavior: smooth; | |
| overflow-x: hidden; | |
| } | |
| .scroll-top { | |
| opacity: 0; | |
| transition: all 0.3s ease; | |
| } | |
| .scroll-top.visible { | |
| opacity: 1; | |
| } | |
| /* Responsive scaling */ | |
| .responsive-content { | |
| width: 100%; | |
| padding: 0 1rem; | |
| box-sizing: border-box; | |
| } | |
| @media (min-width: 640px) { | |
| .responsive-content { | |
| padding: 0 2rem; | |
| } | |
| } | |
| @media (min-width: 1024px) { | |
| .responsive-content { | |
| max-width: 80ch; | |
| margin: 0 auto; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="flex flex-col md:flex-row h-screen"> | |
| <!-- Sidebar --> | |
| <div class="w-full md:w-64 bg-white border-r border-gray-200 overflow-y-auto flex-shrink-0"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <h1 class="text-xl font-bold text-gray-800">Story Collection</h1> | |
| <div class="mt-2 relative"> | |
| <input type="text" id="searchInput" placeholder="Search stories..." | |
| class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
| <i class="fas fa-search absolute right-3 top-2.5 text-gray-400"></i> | |
| </div> | |
| </div> | |
| <div id="storyList" class="divide-y divide-gray-200"> | |
| <!-- Stories will be loaded here --> | |
| <div class="p-4 text-gray-500 text-center"> | |
| <i class="fas fa-book-open text-2xl mb-2"></i> | |
| <p>No stories loaded</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main content --> | |
| <div class="flex-1 flex flex-col overflow-hidden"> | |
| <!-- Toolbar --> | |
| <div class="bg-white border-b border-gray-200 p-4 flex justify-between items-center"> | |
| <div class="min-w-0"> | |
| <h2 id="currentStoryTitle" class="text-lg font-semibold text-gray-800 truncate">No Story Selected</h2> | |
| <p id="storyInfo" class="text-sm text-gray-500 truncate">Select a story to begin reading</p> | |
| </div> | |
| <div class="flex space-x-2 flex-shrink-0"> | |
| <button id="fontSizeDown" class="p-2 rounded-md hover:bg-gray-100 text-gray-700"> | |
| <i class="fas fa-font text-sm"></i> <i class="fas fa-minus text-xs"></i> | |
| </button> | |
| <button id="fontSizeUp" class="p-2 rounded-md hover:bg-gray-100 text-gray-700"> | |
| <i class="fas fa-font text-sm"></i> <i class="fas fa-plus text-xs"></i> | |
| </button> | |
| <button id="darkModeToggle" class="p-2 rounded-md hover:bg-gray-100 text-gray-700"> | |
| <i class="fas fa-moon"></i> | |
| </button> | |
| <button id="scrollTopBtn" class="scroll-top p-2 rounded-md hover:bg-gray-100 text-gray-700"> | |
| <i class="fas fa-arrow-up"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Content area --> | |
| <div id="contentContainer" class="content-container flex-1 overflow-y-auto bg-white"> | |
| <div id="markdownContent" class="markdown-body responsive-content"> | |
| <div class="text-center py-20 text-gray-400"> | |
| <i class="fas fa-book-open text-5xl mb-4"></i> | |
| <h3 class="text-xl font-medium text-gray-500">Select a story from the sidebar</h3> | |
| <p class="mt-2">Or upload your own Markdown file to read</p> | |
| <button id="uploadBtn" class="mt-4 bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition"> | |
| <i class="fas fa-upload mr-2"></i> Upload Markdown | |
| </button> | |
| <input type="file" id="fileInput" class="hidden" accept=".md,.markdown"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="bg-white border-t border-gray-200 p-4 text-center text-sm text-gray-500"> | |
| <p>Markdown Story Viewer • Use arrow keys to navigate</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM elements | |
| const storyList = document.getElementById('storyList'); | |
| const contentContainer = document.getElementById('contentContainer'); | |
| const markdownContent = document.getElementById('markdownContent'); | |
| const currentStoryTitle = document.getElementById('currentStoryTitle'); | |
| const storyInfo = document.getElementById('storyInfo'); | |
| const uploadBtn = document.getElementById('uploadBtn'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const searchInput = document.getElementById('searchInput'); | |
| const fontSizeDown = document.getElementById('fontSizeDown'); | |
| const fontSizeUp = document.getElementById('fontSizeUp'); | |
| const darkModeToggle = document.getElementById('darkModeToggle'); | |
| const scrollTopBtn = document.getElementById('scrollTopBtn'); | |
| // State variables | |
| let stories = []; | |
| let currentStoryIndex = -1; | |
| let fontSize = 16; // base font size in px | |
| let isDarkMode = false; | |
| // Sample stories (could be loaded from an API or localStorage) | |
| const sampleStories = [ | |
| { | |
| id: 'sample1', | |
| title: 'The Adventure Begins', | |
| content: `# The Adventure Begins | |
| ## Chapter 1: A Mysterious Letter | |
| It was a dark and stormy night when the letter arrived. The *wind howled* through the trees as **Professor Langdon** sat by the fireplace in his study. | |
| > "Strange things are afoot," he muttered to himself, adjusting his spectacles. | |
| The letter contained only three words: | |
| 1. **Find** | |
| 2. The | |
| 3. *Orb* | |
| [Continue reading](#)`, | |
| wordCount: 85, | |
| lastUpdated: '2023-05-15' | |
| }, | |
| { | |
| id: 'sample2', | |
| title: 'The Lost City', | |
| content: `# The Lost City | |
| ## Discovery in the Desert | |
| The team had been searching for months when they finally found the entrance to the ancient city. The sandstone walls were covered in strange symbols: | |
| \`\`\` | |
| ▲ ▲ ▼ ▼ ◀ ▶ ◀ ▶ B A | |
| \`\`\` | |
| ### What They Found Inside | |
| - Golden artifacts | |
| - Ancient scrolls | |
| - A map to... somewhere else | |
| `, | |
| wordCount: 62, | |
| lastUpdated: '2023-06-22' | |
| } | |
| ]; | |
| // Initialize with sample stories | |
| loadSampleStories(); | |
| // Event listeners | |
| uploadBtn.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleFileUpload); | |
| searchInput.addEventListener('input', filterStories); | |
| fontSizeDown.addEventListener('click', decreaseFontSize); | |
| fontSizeUp.addEventListener('click', increaseFontSize); | |
| darkModeToggle.addEventListener('click', toggleDarkMode); | |
| scrollTopBtn.addEventListener('click', scrollToTop); | |
| contentContainer.addEventListener('scroll', handleScroll); | |
| // Handle window resize | |
| window.addEventListener('resize', handleResize); | |
| // Keyboard navigation | |
| document.addEventListener('keydown', (e) => { | |
| // Only handle key events when a story is selected | |
| if (currentStoryIndex === -1) return; | |
| switch(e.key) { | |
| case 'ArrowUp': | |
| case 'ArrowLeft': | |
| showPreviousStory(); | |
| break; | |
| case 'ArrowDown': | |
| case 'ArrowRight': | |
| showNextStory(); | |
| break; | |
| case 'Home': | |
| scrollToTop(); | |
| break; | |
| case 'End': | |
| scrollToBottom(); | |
| break; | |
| case '+': | |
| increaseFontSize(); | |
| break; | |
| case '-': | |
| decreaseFontSize(); | |
| break; | |
| case 'd': | |
| case 'D': | |
| if (e.ctrlKey) toggleDarkMode(); | |
| break; | |
| } | |
| }); | |
| // Load sample stories | |
| function loadSampleStories() { | |
| stories = [...sampleStories]; | |
| updateStoryList(); | |
| } | |
| // Handle file upload | |
| function handleFileUpload() { | |
| const file = fileInput.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| const content = e.target.result; | |
| const wordCount = content.split(/\s+/).length; | |
| const newStory = { | |
| id: 'uploaded-' + Date.now(), | |
| title: file.name.replace(/\.[^/.]+$/, ""), // Remove extension | |
| content: content, | |
| wordCount: wordCount, | |
| lastUpdated: new Date().toISOString().split('T')[0] | |
| }; | |
| stories.unshift(newStory); | |
| updateStoryList(); | |
| showStory(0); // Show the newly uploaded story | |
| }; | |
| reader.readAsText(file); | |
| } | |
| // Update the story list in sidebar | |
| function updateStoryList() { | |
| storyList.innerHTML = ''; | |
| if (stories.length === 0) { | |
| storyList.innerHTML = ` | |
| <div class="p-4 text-gray-500 text-center"> | |
| <i class="fas fa-book-open text-2xl mb-2"></i> | |
| <p>No stories available</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| stories.forEach((story, index) => { | |
| const storyElement = document.createElement('div'); | |
| storyElement.className = `sidebar-item p-4 cursor-pointer ${index === currentStoryIndex ? 'active' : ''}`; | |
| storyElement.innerHTML = ` | |
| <h3 class="font-medium text-gray-800 truncate">${story.title}</h3> | |
| <div class="flex justify-between items-center mt-1"> | |
| <span class="text-xs text-gray-500">${story.wordCount} words</span> | |
| <span class="text-xs text-gray-400">${story.lastUpdated}</span> | |
| </div> | |
| `; | |
| storyElement.addEventListener('click', () => { | |
| showStory(index); | |
| }); | |
| storyList.appendChild(storyElement); | |
| }); | |
| } | |
| // Filter stories based on search input | |
| function filterStories() { | |
| const searchTerm = searchInput.value.toLowerCase(); | |
| document.querySelectorAll('.sidebar-item').forEach((item, index) => { | |
| const story = stories[index]; | |
| const matches = story.title.toLowerCase().includes(searchTerm) || | |
| story.content.toLowerCase().includes(searchTerm); | |
| item.style.display = matches ? 'block' : 'none'; | |
| }); | |
| } | |
| // Show story at specified index | |
| function showStory(index) { | |
| if (index < 0 || index >= stories.length) return; | |
| currentStoryIndex = index; | |
| const story = stories[index]; | |
| // Update active item in sidebar | |
| document.querySelectorAll('.sidebar-item').forEach((item, i) => { | |
| if (i === index) { | |
| item.classList.add('active'); | |
| } else { | |
| item.classList.remove('active'); | |
| } | |
| }); | |
| // Update header | |
| currentStoryTitle.textContent = story.title; | |
| storyInfo.textContent = `${story.wordCount} words • Last updated ${story.lastUpdated}`; | |
| // Convert markdown to HTML (simplified - in a real app you might use a library like marked.js) | |
| const htmlContent = convertMarkdownToHtml(story.content); | |
| // Display the content | |
| markdownContent.innerHTML = htmlContent; | |
| markdownContent.style.fontSize = `${fontSize}px`; | |
| // Ensure all content fits without horizontal scrolling | |
| handleResize(); | |
| // Scroll to top | |
| scrollToTop(); | |
| } | |
| // Simple markdown to HTML converter (basic functionality) | |
| function convertMarkdownToHtml(markdown) { | |
| // Headers | |
| let html = markdown | |
| .replace(/^# (.*$)/gm, '<h1>$1</h1>') | |
| .replace(/^## (.*$)/gm, '<h2>$1</h2>') | |
| .replace(/^### (.*$)/gm, '<h3>$1</h3>') | |
| .replace(/^#### (.*$)/gm, '<h4>$1</h4>'); | |
| // Bold and italic | |
| html = html | |
| .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') | |
| .replace(/\*(.*?)\*/g, '<em>$1</em>') | |
| .replace(/\_(.*?)\_/g, '<em>$1</em>'); | |
| // Links | |
| html = html.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>'); | |
| // Images | |
| html = html.replace(/!\[(.*?)\]\((.*?)\)/g, '<img src="$2" alt="$1" class="rounded shadow max-w-full h-auto">'); | |
| // Blockquotes | |
| html = html.replace(/^\> (.*$)/gm, '<blockquote>$1</blockquote>'); | |
| // Lists | |
| html = html.replace(/^\* (.*$)/gm, '<li>$1</li>'); | |
| html = html.replace(/^\- (.*$)/gm, '<li>$1</li>'); | |
| html = html.replace(/^\+ (.*$)/gm, '<li>$1</li>'); | |
| // Code blocks | |
| html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>'); | |
| html = html.replace(/`(.*?)`/g, '<code>$1</code>'); | |
| // Paragraphs (handle line breaks) | |
| html = html.replace(/(^|\n)([^\n].+?)(\n|$)/g, function(match, p1, p2, p3) { | |
| // Don't wrap already wrapped elements | |
| if (p2.startsWith('<') || p2.trim() === '') return match; | |
| return p1 + '<p>' + p2 + '</p>' + p3; | |
| }); | |
| // Horizontal rule | |
| html = html.replace(/^\-\-\-$/gm, '<hr>'); | |
| return html; | |
| } | |
| // Navigation functions | |
| function showPreviousStory() { | |
| if (currentStoryIndex > 0) { | |
| showStory(currentStoryIndex - 1); | |
| } | |
| } | |
| function showNextStory() { | |
| if (currentStoryIndex < stories.length - 1) { | |
| showStory(currentStoryIndex + 1); | |
| } | |
| } | |
| // Font size adjustment | |
| function increaseFontSize() { | |
| if (fontSize < 24) { | |
| fontSize += 1; | |
| markdownContent.style.fontSize = `${fontSize}px`; | |
| handleResize(); | |
| } | |
| } | |
| function decreaseFontSize() { | |
| if (fontSize > 12) { | |
| fontSize -= 1; | |
| markdownContent.style.fontSize = `${fontSize}px`; | |
| handleResize(); | |
| } | |
| } | |
| // Dark mode toggle | |
| function toggleDarkMode() { | |
| isDarkMode = !isDarkMode; | |
| if (isDarkMode) { | |
| document.body.classList.add('bg-gray-900'); | |
| document.body.classList.remove('bg-gray-50'); | |
| markdownContent.classList.add('text-gray-200'); | |
| markdownContent.classList.remove('text-gray-800'); | |
| darkModeToggle.innerHTML = '<i class="fas fa-sun"></i>'; | |
| // Update all markdown elements | |
| const elements = ['h1', 'h2', 'h3', 'h4', 'p', 'li', 'blockquote', 'code', 'a']; | |
| elements.forEach(el => { | |
| document.querySelectorAll(`.markdown-body ${el}`).forEach(item => { | |
| if (el === 'a') { | |
| item.classList.add('text-blue-400'); | |
| item.classList.remove('text-blue-600'); | |
| } else if (el === 'code') { | |
| // No change for code | |
| } else { | |
| item.classList.add('text-gray-300'); | |
| item.classList.remove('text-gray-800'); | |
| } | |
| }); | |
| }); | |
| document.querySelectorAll('.markdown-body pre').forEach(pre => { | |
| pre.classList.add('bg-gray-800'); | |
| pre.classList.remove('bg-gray-100'); | |
| }); | |
| } else { | |
| document.body.classList.remove('bg-gray-900'); | |
| document.body.classList.add('bg-gray-50'); | |
| markdownContent.classList.remove('text-gray-200'); | |
| markdownContent.classList.add('text-gray-800'); | |
| darkModeToggle.innerHTML = '<i class="fas fa-moon"></i>'; | |
| // Revert all markdown elements | |
| const elements = ['h1', 'h2', 'h3', 'h4', 'p', 'li', 'blockquote', 'code', 'a']; | |
| elements.forEach(el => { | |
| document.querySelectorAll(`.markdown-body ${el}`).forEach(item => { | |
| if (el === 'a') { | |
| item.classList.remove('text-blue-400'); | |
| item.classList.add('text-blue-600'); | |
| } else if (el === 'code') { | |
| // No change for code | |
| } else { | |
| item.classList.remove('text-gray-300'); | |
| item.classList.add('text-gray-800'); | |
| } | |
| }); | |
| }); | |
| document.querySelectorAll('.markdown-body pre').forEach(pre => { | |
| pre.classList.remove('bg-gray-800'); | |
| pre.classList.add('bg-gray-100'); | |
| }); | |
| } | |
| } | |
| // Scroll functions | |
| function scrollToTop() { | |
| contentContainer.scrollTo({ | |
| top: 0, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| function scrollToBottom() { | |
| contentContainer.scrollTo({ | |
| top: contentContainer.scrollHeight, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| function handleScroll() { | |
| // Show/hide scroll to top button | |
| if (contentContainer.scrollTop > 300) { | |
| scrollTopBtn.classList.add('visible'); | |
| } else { | |
| scrollTopBtn.classList.remove('visible'); | |
| } | |
| } | |
| // Handle window resize and ensure content fits | |
| function handleResize() { | |
| // Ensure code blocks don't cause horizontal scrolling | |
| document.querySelectorAll('.markdown-body pre').forEach(pre => { | |
| pre.style.maxWidth = '100%'; | |
| pre.style.overflowX = 'auto'; | |
| }); | |
| // Ensure images don't overflow | |
| document.querySelectorAll('.markdown-body img').forEach(img => { | |
| img.style.maxWidth = '100%'; | |
| img.style.height = 'auto'; | |
| }); | |
| // Ensure tables are scrollable if needed | |
| document.querySelectorAll('.markdown-body table').forEach(table => { | |
| table.style.display = 'block'; | |
| table.style.overflowX = 'auto'; | |
| table.style.whiteSpace = 'nowrap'; | |
| }); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=jsfs11/md-reader" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |