|
|
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Markdown to DOCX Converter</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/lib/core.min.js"></script> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/styles/github.min.css"> |
| <style> |
| .editor-container { |
| height: calc(100vh - 200px); |
| } |
| #markdown-input, #markdown-preview { |
| height: 100%; |
| overflow-y: auto; |
| } |
| #markdown-preview { |
| border-left: 1px solid #e2e8f0; |
| } |
| .hljs { |
| background: #f8fafc; |
| border-radius: 0.25rem; |
| padding: 1rem; |
| } |
| .hljs pre { |
| margin: 0; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50"> |
| <div class="container mx-auto px-4 py-8"> |
| <header class="mb-8 text-center"> |
| <h1 class="text-3xl font-bold text-gray-800 mb-2">Markdown to DOCX Converter</h1> |
| <p class="text-gray-600">Write your Markdown and convert it to a professional DOCX document</p> |
| </header> |
|
|
| <div class="bg-white rounded-lg shadow-md overflow-hidden mb-6"> |
| <div class="flex justify-between items-center bg-gray-100 px-4 py-3 border-b border-gray-200"> |
| <h2 class="font-semibold text-gray-700">Editor</h2> |
| <div class="flex space-x-2"> |
| <button id="preview-toggle" class="px-3 py-1 bg-gray-200 text-gray-700 rounded-md text-sm hover:bg-gray-300 md:hidden"> |
| Toggle Preview |
| </button> |
| <button id="convert-btn" class="px-3 py-1 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700 flex items-center"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> |
| </svg> |
| Convert to DOCX |
| </button> |
| </div> |
| </div> |
| |
| <div class="flex flex-col md:flex-row editor-container"> |
| <div class="w-full md:w-1/2 p-4"> |
| <textarea id="markdown-input" class="w-full h-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none" placeholder="# Start writing your Markdown here... |
| |
| ## Headers |
| Use # for h1, ## for h2 etc. |
| |
| ## Lists |
| - Item 1 |
| - Item 2 |
| |
| ## Code |
| ```javascript |
| console.log('Hello World'); |
| ``` |
| |
| ## More formatting |
| **Bold**, *italic*, [links](https://example.com)"></textarea> |
| </div> |
| <div id="markdown-preview" class="w-full md:w-1/2 p-4 prose max-w-none"></div> |
| </div> |
| </div> |
|
|
| <div id="status-message" class="hidden fixed bottom-4 right-4 bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded"></div> |
| </div> |
|
|
| <script> |
| |
| marked.setOptions({ |
| highlight: function(code, lang) { |
| const language = hljs.getLanguage(lang) ? lang : 'plaintext'; |
| return hljs.highlight(code, { language }).value; |
| }, |
| langPrefix: 'hljs language-', |
| breaks: true, |
| gfm: true |
| }); |
| |
| |
| const markdownInput = document.getElementById('markdown-input'); |
| const markdownPreview = document.getElementById('markdown-preview'); |
| const convertBtn = document.getElementById('convert-btn'); |
| const previewToggle = document.getElementById('preview-toggle'); |
| const statusMessage = document.getElementById('status-message'); |
| |
| |
| function updatePreview() { |
| markdownPreview.innerHTML = marked.parse(markdownInput.value); |
| } |
| |
| |
| updatePreview(); |
| |
| |
| markdownInput.addEventListener('input', updatePreview); |
| |
| previewToggle.addEventListener('click', () => { |
| markdownPreview.classList.toggle('hidden'); |
| markdownInput.classList.toggle('w-full'); |
| }); |
| |
| convertBtn.addEventListener('click', async () => { |
| const markdown = markdownInput.value.trim(); |
| |
| if (!markdown) { |
| showStatus('Please enter some Markdown content', 'error'); |
| return; |
| } |
| |
| try { |
| convertBtn.disabled = true; |
| convertBtn.innerHTML = ` |
| <svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
| <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
| <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
| </svg> |
| Converting... |
| `; |
| |
| showStatus('Converting to DOCX...', 'info'); |
| |
| |
| const response = await fetch('/api/convert', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| markdown: markdown |
| }) |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`Server returned ${response.status}`); |
| } |
| |
| const result = await response.json(); |
| |
| if (!result.success) { |
| throw new Error(result.message || 'Conversion failed'); |
| } |
| |
| |
| const downloadResponse = await fetch(`/api/download/${result.filename}`); |
| |
| if (!downloadResponse.ok) { |
| throw new Error('Failed to download file'); |
| } |
| |
| const blob = await downloadResponse.blob(); |
| const url = window.URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = result.filename || 'document.docx'; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| window.URL.revokeObjectURL(url); |
| |
| showStatus('Document downloaded successfully!', 'success'); |
| } catch (error) { |
| console.error('Conversion error:', error); |
| showStatus('Error converting document: ' + error.message, 'error'); |
| } finally { |
| convertBtn.disabled = false; |
| convertBtn.innerHTML = ` |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> |
| </svg> |
| Convert to DOCX |
| `; |
| } |
| }); |
| |
| function showStatus(message, type) { |
| statusMessage.textContent = message; |
| statusMessage.className = `fixed bottom-4 right-4 px-4 py-3 rounded`; |
| |
| if (type === 'error') { |
| statusMessage.classList.add('bg-red-100', 'border-red-400', 'text-red-700'); |
| } else if (type === 'success') { |
| statusMessage.classList.add('bg-green-100', 'border-green-400', 'text-green-700'); |
| } else { |
| statusMessage.classList.add('bg-blue-100', 'border-blue-400', 'text-blue-700'); |
| } |
| |
| statusMessage.classList.remove('hidden'); |
| |
| setTimeout(() => { |
| statusMessage.classList.add('hidden'); |
| }, 5000); |
| } |
| </script> |
| </body> |
| </html> |
|
|