feat: Add Forge Document mode to Markdown Converter
- Backend: - Added 'forge' output format support (maps to HTML with Forge theme). - Implemented 'forge' theme with Montserrat font and white-paper styling. - Fixed 'Plain Text' mode not returning output. - Added fallback 'output' return when markdown library is missing. - Frontend: - Added 'Forge Document' as the default output format. - Implemented 'Copy Formatted' button for rich text clipboard support (Word/Excel compatible). - Switched to single-column layout for better document visibility. - Used iframe for document preview to isolate styles and prevent layout issues.
This commit is contained in:
parent
d7852fc399
commit
3f88af3258
2 changed files with 88 additions and 37 deletions
|
|
@ -415,6 +415,10 @@ async def convert_markdown(
|
|||
Dictionary with converted content
|
||||
"""
|
||||
try:
|
||||
if output_format == "forge":
|
||||
output_format = "html"
|
||||
theme = "forge"
|
||||
|
||||
import markdown
|
||||
from markdown.extensions import tables, fenced_code, toc
|
||||
|
||||
|
|
@ -429,12 +433,20 @@ async def convert_markdown(
|
|||
])
|
||||
html = md.convert(content)
|
||||
|
||||
# Add basic styling
|
||||
# Define styles based on theme
|
||||
extra_head = ""
|
||||
font_family = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
||||
|
||||
if theme == "forge":
|
||||
extra_head = '<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">'
|
||||
font_family = "'Montserrat', sans-serif"
|
||||
|
||||
styled_html = f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{extra_head}
|
||||
<style>
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }}
|
||||
body {{ font-family: {font_family}; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }}
|
||||
code {{ background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }}
|
||||
pre {{ background: #f4f4f4; padding: 16px; border-radius: 6px; overflow-x: auto; }}
|
||||
table {{ border-collapse: collapse; width: 100%; }}
|
||||
|
|
@ -470,6 +482,7 @@ blockquote {{ border-left: 4px solid #ddd; margin: 0; padding-left: 16px; color:
|
|||
|
||||
return {
|
||||
"success": True,
|
||||
"output": text.strip(),
|
||||
"content": text.strip(),
|
||||
"format": "plain"
|
||||
}
|
||||
|
|
@ -484,6 +497,7 @@ blockquote {{ border-left: 4px solid #ddd; margin: 0; padding-left: 16px; color:
|
|||
# Fallback without markdown library
|
||||
return {
|
||||
"success": True,
|
||||
"output": content,
|
||||
"content": content,
|
||||
"format": output_format,
|
||||
"note": "markdown library not installed"
|
||||
|
|
|
|||
|
|
@ -6,18 +6,20 @@ import { FileText, Download, Sparkles, Copy } from 'lucide-react';
|
|||
import { modulesApi } from '@/lib/api';
|
||||
|
||||
const outputFormats = [
|
||||
{ value: 'forge', label: 'Forge Document' },
|
||||
{ value: 'html', label: 'HTML' },
|
||||
{ value: 'plain', label: 'Plain Text' },
|
||||
];
|
||||
|
||||
const themes = [
|
||||
{ value: 'forge', label: 'Forge' },
|
||||
{ value: 'github', label: 'GitHub' },
|
||||
{ value: 'default', label: 'Default' },
|
||||
];
|
||||
|
||||
export default function MarkdownConverterPage() {
|
||||
const [content, setContent] = useState('');
|
||||
const [outputFormat, setOutputFormat] = useState('html');
|
||||
const [outputFormat, setOutputFormat] = useState('forge');
|
||||
const [theme, setTheme] = useState('github');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [result, setResult] = useState<any>(null);
|
||||
|
|
@ -48,7 +50,26 @@ export default function MarkdownConverterPage() {
|
|||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
if (result?.output) {
|
||||
if (!result?.output) return;
|
||||
|
||||
if (outputFormat === 'forge') {
|
||||
try {
|
||||
const blob = new Blob([result.output], { type: 'text/html' });
|
||||
const plainBlob = new Blob([result.output], { type: 'text/plain' });
|
||||
|
||||
navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'text/html': blob,
|
||||
'text/plain': plainBlob,
|
||||
})
|
||||
]);
|
||||
toast.success('Copied formatted text!');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
navigator.clipboard.writeText(result.output);
|
||||
toast.success('Copied HTML source (Rich copy failed)');
|
||||
}
|
||||
} else {
|
||||
navigator.clipboard.writeText(result.output);
|
||||
toast.success('Copied to clipboard!');
|
||||
}
|
||||
|
|
@ -81,7 +102,7 @@ export default function MarkdownConverterPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div className="grid grid-cols-1 gap-8">
|
||||
{/* Controls */}
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
|
|
@ -149,39 +170,55 @@ export default function MarkdownConverterPage() {
|
|||
|
||||
{result?.output ? (
|
||||
<>
|
||||
<div className="bg-forge-dark rounded-lg p-4 border border-gray-800">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs text-gray-500 font-mono">
|
||||
{outputFormat === 'html' ? 'HTML Output' : 'Plain Text'}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="text-xs text-forge-yellow hover:text-forge-yellow/80 flex items-center gap-1"
|
||||
>
|
||||
<Copy className="w-3 h-3" />
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="text-xs text-forge-yellow hover:text-forge-yellow/80 flex items-center gap-1"
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="text-sm text-gray-300 overflow-x-auto max-h-[400px]">
|
||||
<code>{result.output}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{outputFormat === 'html' && (
|
||||
{outputFormat !== 'forge' && (
|
||||
<div className="bg-forge-dark rounded-lg p-4 border border-gray-800">
|
||||
<p className="text-xs text-gray-500 mb-2">Preview</p>
|
||||
<div
|
||||
className="prose prose-invert max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: result.output }}
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs text-gray-500 font-mono">
|
||||
{outputFormat === 'html' ? 'HTML Output' : 'Plain Text'}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="text-xs text-forge-yellow hover:text-forge-yellow/80 flex items-center gap-1"
|
||||
>
|
||||
<Copy className="w-3 h-3" />
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="text-xs text-forge-yellow hover:text-forge-yellow/80 flex items-center gap-1"
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="text-sm text-gray-300 overflow-x-auto max-h-[400px]">
|
||||
<code>{result.output}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(outputFormat === 'html' || outputFormat === 'forge') && (
|
||||
<div className={`rounded-lg p-8 border border-gray-800 ${outputFormat === 'forge' ? 'bg-white text-black shadow-xl' : 'bg-forge-dark'
|
||||
}`}>
|
||||
<div className="flex justify-between items-center mb-4 border-b border-gray-200 pb-2">
|
||||
<p className={`text-xs font-semibold ${outputFormat === 'forge' ? 'text-gray-500' : 'text-gray-500'}`}>Preview</p>
|
||||
{outputFormat === 'forge' && (
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="text-xs flex items-center gap-2 font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 px-3 py-1.5 rounded-md transition-colors border border-gray-300"
|
||||
title="Copy styled content for Word/Excel"
|
||||
>
|
||||
<Copy className="w-3 h-3" />
|
||||
Copy Formatted
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<iframe
|
||||
srcDoc={result.output}
|
||||
className="w-full h-[800px] bg-white rounded-lg shadow-inner"
|
||||
title="Document Preview"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue