368426f0f4
- Added `app.py` with Flask backend for real-time Markdown to Word conversion using python-docx - Implemented `convert_md_to_docx` function with support for headings, tables, code blocks, images, and blockquote styling - Created `templates/index.html` with responsive UI featuring dual-panel editor/preview layout - Added `static/script.js` for client-side functionality including live preview, toolbar buttons, and export workflow - Enhanced `static/style.css` with modern styling for editor, preview, and settings panels - Updated `README.md` to document features, usage, and customization options - Added `requirements.txt` with dependencies: Flask, python-docx, markdown - Implemented auto-captioning for images and tables with sequential numbering - Added table pagination support with header repetition across page breaks - Integrated configuration settings panel for customizing styles, captions, and document options
298 lines
11 KiB
JavaScript
298 lines
11 KiB
JavaScript
// GhostEditor - Markdown to Word Converter JavaScript
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// DOM Elements
|
|
const markdownInput = document.getElementById('markdownInput');
|
|
const previewOutput = document.getElementById('previewOutput');
|
|
const previewToggle = document.getElementById('previewToggle');
|
|
const exportBtn = document.getElementById('exportBtn');
|
|
const blockType = document.getElementById('blockType');
|
|
const applyStyle = document.getElementById('applyStyle');
|
|
const applyStyleBtn = document.getElementById('applyStyleBtn');
|
|
const customCaption = document.getElementById('customCaption');
|
|
const applyCaptionBtn = document.getElementById('applyCaptionBtn');
|
|
const autoCaptionImages = document.getElementById('autoCaptionImages');
|
|
const autoCaptionTables = document.getElementById('autoCaptionTables');
|
|
const autoCaptionFigures = document.getElementById('autoCaptionFigures');
|
|
const tablePagination = document.getElementById('tablePagination');
|
|
const boldBtn = document.getElementById('boldBtn');
|
|
const italicBtn = document.getElementById('italicBtn');
|
|
const headingBtn = document.getElementById('headingBtn');
|
|
const listBtn = document.getElementById('listBtn');
|
|
const imageBtn = document.getElementById('imageBtn');
|
|
|
|
// Configuration
|
|
let config = {
|
|
autoCaptionImages: true,
|
|
autoCaptionTables: true,
|
|
autoCaptionFigures: true,
|
|
tablePagination: true,
|
|
defaultStyle: 'Normal'
|
|
};
|
|
|
|
// Initialize with sample markdown
|
|
markdownInput.value = `# GhostEditor Sample Document
|
|
|
|
This is a sample document to demonstrate the real-time conversion capabilities of GhostEditor.
|
|
|
|
## Features Demonstrated
|
|
|
|
### Auto-captioning
|
|

|
|
*Figure 1: Sample placeholder image*
|
|
|
|
### Table with Pagination Support
|
|
| ID | Name | Role |
|
|
|----|------|------|
|
|
| 1 | John | Developer |
|
|
| 2 | Jane | Designer |
|
|
| 3 | Bob | Manager |
|
|
| 4 | Alice | Analyst |
|
|
|
|
### Code Block
|
|
\`\`\`python
|
|
def hello_world():
|
|
print("Hello, GhostEditor!")
|
|
\`\`\`
|
|
|
|
### Blockquote
|
|
> This is a sample quote demonstrating the styling capabilities of GhostEditor.
|
|
|
|
### List
|
|
- First item
|
|
- Second item
|
|
- Third item`;
|
|
|
|
// Update configuration when settings change
|
|
autoCaptionImages.addEventListener('change', updateConfig);
|
|
autoCaptionTables.addEventListener('change', updateConfig);
|
|
autoCaptionFigures.addEventListener('change', updateConfig);
|
|
tablePagination.addEventListener('change', updateConfig);
|
|
|
|
// Toolbar buttons
|
|
boldBtn.addEventListener('click', () => wrapText('**', '**'));
|
|
italicBtn.addEventListener('click', () => wrapText('*', '*'));
|
|
headingBtn.addEventListener('click', () => wrapLine('# '));
|
|
listBtn.addEventListener('click', () => wrapLine('- '));
|
|
imageBtn.addEventListener('click', () => insertImage());
|
|
|
|
// Apply style button
|
|
applyStyleBtn.addEventListener('click', applyStyleToSelection);
|
|
|
|
// Apply caption button
|
|
applyCaptionBtn.addEventListener('click', applyCaptionToSelection);
|
|
|
|
// Export button
|
|
exportBtn.addEventListener('click', exportToWord);
|
|
|
|
// Initial preview update
|
|
updatePreview();
|
|
|
|
// Update preview when input changes
|
|
markdownInput.addEventListener('input', updatePreview);
|
|
|
|
// Update preview when toggle changes
|
|
previewToggle.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
updatePreview();
|
|
} else {
|
|
previewOutput.innerHTML = '<p class="text-muted">Preview is disabled. Enable to see real-time conversion.</p>';
|
|
}
|
|
});
|
|
|
|
// Update configuration
|
|
function updateConfig() {
|
|
config = {
|
|
autoCaptionImages: autoCaptionImages.checked,
|
|
autoCaptionTables: autoCaptionTables.checked,
|
|
autoCaptionFigures: autoCaptionFigures.checked,
|
|
tablePagination: tablePagination.checked,
|
|
defaultStyle: document.getElementById('defaultStyle').value
|
|
};
|
|
|
|
// Update preview after config change
|
|
updatePreview();
|
|
}
|
|
|
|
// Update preview function
|
|
function updatePreview() {
|
|
if (!previewToggle.checked) return;
|
|
|
|
const markdown = markdownInput.value;
|
|
|
|
// Convert markdown to HTML using Marked.js
|
|
const html = marked.parse(markdown);
|
|
|
|
// Process the HTML to add captions and table pagination
|
|
const processedHtml = processHtmlForPreview(html);
|
|
|
|
previewOutput.innerHTML = processedHtml;
|
|
|
|
// Add captions to images if enabled
|
|
if (config.autoCaptionImages) {
|
|
addImageCaptions();
|
|
}
|
|
}
|
|
|
|
// Process HTML for preview with special features
|
|
function processHtmlForPreview(html) {
|
|
let processed = html;
|
|
|
|
// Add table pagination header if enabled
|
|
if (config.tablePagination) {
|
|
processed = processed.replace(/<table>/g, '<table class="table-pagination">');
|
|
}
|
|
|
|
return processed;
|
|
}
|
|
|
|
// Add captions to images
|
|
function addImageCaptions() {
|
|
const images = previewOutput.querySelectorAll('img');
|
|
images.forEach((img, index) => {
|
|
// Check if image already has a caption
|
|
if (!img.nextElementSibling || !img.nextElementSibling.classList.contains('caption')) {
|
|
const caption = document.createElement('div');
|
|
caption.className = 'caption';
|
|
caption.textContent = `Figure ${index + 1}: ${img.alt || 'Image'}`;
|
|
img.parentNode.insertBefore(caption, img.nextSibling);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Apply style to selected text
|
|
function applyStyleToSelection() {
|
|
const selectedStyle = applyStyle.value;
|
|
const selectedBlock = blockType.value;
|
|
|
|
// In a real implementation, this would modify the markdown
|
|
// For now, we'll just show a preview of the style
|
|
showStylePreview(selectedStyle);
|
|
}
|
|
|
|
// Show style preview
|
|
function showStylePreview(style) {
|
|
const previewDiv = document.getElementById('stylePreview');
|
|
|
|
let styleExample = '';
|
|
switch(style) {
|
|
case 'Heading1':
|
|
styleExample = '<h1>Heading 1 Example</h1>';
|
|
break;
|
|
case 'Heading2':
|
|
styleExample = '<h2>Heading 2 Example</h2>';
|
|
break;
|
|
case 'Heading3':
|
|
styleExample = '<h3>Heading 3 Example</h3>';
|
|
break;
|
|
case 'Quote':
|
|
styleExample = '<blockquote>This is a quote example</blockquote>';
|
|
break;
|
|
case 'Code':
|
|
styleExample = '<code>This is a code example</code>';
|
|
break;
|
|
default:
|
|
styleExample = '<p>This is a normal paragraph example</p>';
|
|
}
|
|
|
|
previewDiv.innerHTML = styleExample;
|
|
}
|
|
|
|
// Apply caption to selection
|
|
function applyCaptionToSelection() {
|
|
const captionText = customCaption.value.trim();
|
|
if (!captionText) return;
|
|
|
|
// In a real implementation, this would add the caption to the markdown
|
|
// For now, we'll just show a notification
|
|
alert(`Caption applied: ${captionText}`);
|
|
}
|
|
|
|
// Wrap selected text with markdown syntax
|
|
function wrapText(before, after) {
|
|
const start = markdownInput.selectionStart;
|
|
const end = markdownInput.selectionEnd;
|
|
const selectedText = markdownInput.value.substring(start, end);
|
|
|
|
const newText = before + selectedText + after;
|
|
markdownInput.value = markdownInput.value.substring(0, start) + newText + markdownInput.value.substring(end);
|
|
|
|
// Update cursor position
|
|
markdownInput.selectionStart = start + before.length;
|
|
markdownInput.selectionEnd = start + before.length + selectedText.length;
|
|
|
|
updatePreview();
|
|
}
|
|
|
|
// Wrap current line with markdown syntax
|
|
function wrapLine(prefix) {
|
|
const start = markdownInput.selectionStart;
|
|
const end = markdownInput.selectionEnd;
|
|
const text = markdownInput.value;
|
|
|
|
// Find start and end of current line
|
|
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
|
const lineEnd = text.indexOf('\n', end);
|
|
const actualLineEnd = lineEnd === -1 ? text.length : lineEnd;
|
|
|
|
const lineText = text.substring(lineStart, actualLineEnd);
|
|
const newLineText = prefix + lineText;
|
|
|
|
markdownInput.value = text.substring(0, lineStart) + newLineText + text.substring(actualLineEnd);
|
|
|
|
// Update cursor position
|
|
markdownInput.selectionStart = start + prefix.length;
|
|
markdownInput.selectionEnd = end + prefix.length;
|
|
|
|
updatePreview();
|
|
}
|
|
|
|
// Insert image markdown
|
|
function insertImage() {
|
|
const start = markdownInput.selectionStart;
|
|
const end = markdownInput.selectionEnd;
|
|
const selectedText = markdownInput.value.substring(start, end);
|
|
|
|
const altText = selectedText || 'image';
|
|
const imageMd = ``;
|
|
|
|
markdownInput.value = markdownInput.value.substring(0, start) + imageMd + markdownInput.value.substring(end);
|
|
|
|
// Update cursor position
|
|
markdownInput.selectionStart = start + 2; // Position cursor in the alt text
|
|
markdownInput.selectionEnd = start + 2 + altText.length;
|
|
|
|
updatePreview();
|
|
}
|
|
|
|
// Export to Word functionality
|
|
function exportToWord() {
|
|
// In a real implementation, this would send the markdown to the backend
|
|
// to convert it to Word format
|
|
alert('Exporting to Word document...');
|
|
|
|
// Create a form to submit the markdown to the backend
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = '/export';
|
|
form.style.display = 'none';
|
|
|
|
const markdownField = document.createElement('input');
|
|
markdownField.type = 'hidden';
|
|
markdownField.name = 'markdown';
|
|
markdownField.value = markdownInput.value;
|
|
|
|
const configField = document.createElement('input');
|
|
configField.type = 'hidden';
|
|
configField.name = 'config';
|
|
configField.value = JSON.stringify(config);
|
|
|
|
form.appendChild(markdownField);
|
|
form.appendChild(configField);
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
document.body.removeChild(form);
|
|
}
|
|
|
|
// Initialize style preview
|
|
showStylePreview(config.defaultStyle);
|
|
}); |