Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -77,7 +77,7 @@ def parse_endpoint():
|
|
| 77 |
except Exception as e:
|
| 78 |
return jsonify({'error': f'Failed to parse: {e}'}), 500
|
| 79 |
|
| 80 |
-
# --- HTML & PNG BUILDER (Unchanged
|
| 81 |
def build_full_html(markdown_text, styles, include_fontawesome):
|
| 82 |
wrapper_id = "#output-wrapper"
|
| 83 |
font_family = styles.get('font_family', "'Arial', sans-serif")
|
|
@@ -97,8 +97,6 @@ def build_full_html(markdown_text, styles, include_fontawesome):
|
|
| 97 |
font-family: {font_family}; font-size: {styles.get('font_size', '16')}px;
|
| 98 |
color: {styles.get('text_color', '#333')}; background-color: {styles.get('background_color', '#fff')};
|
| 99 |
}}
|
| 100 |
-
/* ... other scoped styles ... */
|
| 101 |
-
|
| 102 |
{wrapper_id} table {{ border-collapse: collapse; width: 100%; }}
|
| 103 |
{wrapper_id} th, {wrapper_id} td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
| 104 |
{wrapper_id} th {{ background-color: #f2f2f2; }}
|
|
@@ -127,7 +125,7 @@ def build_full_html(markdown_text, styles, include_fontawesome):
|
|
| 127 |
@app.route('/convert', methods=['POST'])
|
| 128 |
def convert_endpoint():
|
| 129 |
data = request.json
|
| 130 |
-
temp_html_path = None
|
| 131 |
try:
|
| 132 |
full_html = build_full_html(
|
| 133 |
markdown_text=data.get('markdown_text', ''),
|
|
@@ -135,10 +133,14 @@ def convert_endpoint():
|
|
| 135 |
include_fontawesome=data.get('include_fontawesome', False)
|
| 136 |
)
|
| 137 |
|
| 138 |
-
#
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
-
# --- REQUIRED CHANGE 2: Manually create and use a temporary file ---
|
| 142 |
temp_html_path = os.path.join(TEMP_DIR, f"{uuid.uuid4()}.html")
|
| 143 |
with open(temp_html_path, "w", encoding="utf-8") as f:
|
| 144 |
f.write(full_html)
|
|
@@ -148,11 +150,9 @@ def convert_endpoint():
|
|
| 148 |
if download_type == 'html':
|
| 149 |
return send_file(BytesIO(full_html.encode("utf-8")), as_attachment=True, download_name="output.html", mimetype="text/html")
|
| 150 |
else:
|
| 151 |
-
# Use from_file instead of from_string
|
| 152 |
png_bytes = imgkit.from_file(temp_html_path, False, options=options)
|
| 153 |
return send_file(BytesIO(png_bytes), as_attachment=True, download_name="output.png", mimetype="image/png")
|
| 154 |
else:
|
| 155 |
-
# Use from_file instead of from_string
|
| 156 |
png_bytes = imgkit.from_file(temp_html_path, False, options=options)
|
| 157 |
png_base64 = base64.b64encode(png_bytes).decode('utf-8')
|
| 158 |
return jsonify({'preview_html': full_html, 'preview_png_base64': png_base64})
|
|
@@ -161,11 +161,10 @@ def convert_endpoint():
|
|
| 161 |
traceback.print_exc()
|
| 162 |
return jsonify({'error': f'Failed to convert content: {str(e)}'}), 500
|
| 163 |
finally:
|
| 164 |
-
# --- REQUIRED CHANGE 3: Clean up the temporary file ---
|
| 165 |
if temp_html_path and os.path.exists(temp_html_path):
|
| 166 |
os.remove(temp_html_path)
|
| 167 |
|
| 168 |
-
# --- MAIN PAGE RENDERER (
|
| 169 |
@app.route('/')
|
| 170 |
def index():
|
| 171 |
highlight_styles = sorted(list(get_all_styles()))
|
|
@@ -197,14 +196,13 @@ def index():
|
|
| 197 |
.component-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; }
|
| 198 |
.component-container { border: 1px solid #e0e0e0; border-radius: 5px; background: #fafafa; }
|
| 199 |
.component-header { background: #f1f1f1; padding: 8px 12px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; gap: 10px; }
|
| 200 |
-
.component-content textarea { height: 150px; }
|
| 201 |
.selection-controls { margin: 15px 0; display: flex; gap: 10px; }
|
| 202 |
</style>
|
| 203 |
</head>
|
| 204 |
<body>
|
| 205 |
<h1>Intelligent Markdown Converter</h1>
|
| 206 |
<form id="main-form" onsubmit="return false;">
|
| 207 |
-
<!-- Input and Styling sections -->
|
| 208 |
<fieldset><legend>1. Load Content</legend><div id="info-box" class="info"></div><textarea id="markdown-text-input" name="markdown_text" rows="8"></textarea><div style="margin-top: 10px; display: flex; align-items: center; gap: 10px;"><label for="markdown-file-input">Or upload a file:</label><input type="file" id="markdown-file-input" name="markdown_file" accept=".md,.txt,text/markdown"></div><div style="margin-top: 15px;"><button type="button" id="load-btn" class="action-btn">Load & Analyze</button></div></fieldset>
|
| 209 |
<fieldset id="components-fieldset" style="display:none;"><legend>2. Select Components</legend><div class="selection-controls"><button type="button" onclick="toggleAllComponents(true)">Select All</button><button type="button" onclick="toggleAllComponents(false)">Deselect All</button></div><div id="components-container" class="component-grid"></div></fieldset>
|
| 210 |
<fieldset><legend>3. Configure Styles</legend><div class="style-grid"><div><label>Font Family:</label><select id="font_family"><optgroup label="Sans-Serif"><option value="'Arial', sans-serif">Arial</option><option value="'Roboto', sans-serif">Roboto</option></optgroup><optgroup label="Serif"><option value="'Times New Roman', serif">Times New Roman</option><option value="'Georgia', serif">Georgia</option></optgroup></select></div><div><label>Font Size (px):</label><input type="number" id="font_size" value="16"></div><div><label>Highlight Theme:</label><select id="highlight_theme"><option value="none">None</option>{% for style in highlight_styles %}<option value="{{ style }}" {% if style == 'default' %}selected{% endif %}>{{ style }}</option>{% endfor %}</select></div><div><label>Text Color:</label><input type="color" id="text_color" value="#333333"></div><div><label>Background Color:</label><input type="color" id="background_color" value="#ffffff"></div><div><label>Code Padding (px):</label><input type="number" id="code_padding" value="15"></div></div><div><input type="checkbox" id="include_fontawesome"><label for="include_fontawesome">Include Font Awesome</label></div><div><label for="custom_css">Custom CSS:</label><textarea id="custom_css" rows="3"></textarea></div></fieldset>
|
|
@@ -227,8 +225,6 @@ def index():
|
|
| 227 |
<div id="png-preview-container" class="preview-container"></div>
|
| 228 |
</div>
|
| 229 |
<script>
|
| 230 |
-
// --- All JavaScript is unchanged from the previous correct version ---
|
| 231 |
-
// It correctly gathers style info without modifying the parent page.
|
| 232 |
const loadBtn = document.getElementById('load-btn'), generateBtn = document.getElementById('generate-btn'),
|
| 233 |
downloadHtmlBtn = document.getElementById('download-html-btn'), downloadPngBtn = document.getElementById('download-png-btn'),
|
| 234 |
markdownTextInput = document.getElementById('markdown-text-input'), markdownFileInput = document.getElementById('markdown-file-input'),
|
|
@@ -258,7 +254,6 @@ def index():
|
|
| 258 |
include_fontawesome: document.getElementById('include_fontawesome').checked,
|
| 259 |
};
|
| 260 |
}
|
| 261 |
-
loadBtn.addEventListener('click', async () => { /* Logic unchanged */ });
|
| 262 |
generateBtn.addEventListener('click', async () => {
|
| 263 |
generateBtn.textContent = 'Generating...'; generateBtn.disabled = true; errorBox.style.display = 'none';
|
| 264 |
const payload = buildPayload();
|
|
@@ -318,6 +313,4 @@ def index():
|
|
| 318 |
""", highlight_styles=highlight_styles)
|
| 319 |
|
| 320 |
if __name__ == "__main__":
|
| 321 |
-
# Ensure you have installed the required libraries:
|
| 322 |
-
# pip install Flask markdown imgkit pygments
|
| 323 |
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))
|
|
|
|
| 77 |
except Exception as e:
|
| 78 |
return jsonify({'error': f'Failed to parse: {e}'}), 500
|
| 79 |
|
| 80 |
+
# --- HTML & PNG BUILDER (Unchanged) ---
|
| 81 |
def build_full_html(markdown_text, styles, include_fontawesome):
|
| 82 |
wrapper_id = "#output-wrapper"
|
| 83 |
font_family = styles.get('font_family', "'Arial', sans-serif")
|
|
|
|
| 97 |
font-family: {font_family}; font-size: {styles.get('font_size', '16')}px;
|
| 98 |
color: {styles.get('text_color', '#333')}; background-color: {styles.get('background_color', '#fff')};
|
| 99 |
}}
|
|
|
|
|
|
|
| 100 |
{wrapper_id} table {{ border-collapse: collapse; width: 100%; }}
|
| 101 |
{wrapper_id} th, {wrapper_id} td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
| 102 |
{wrapper_id} th {{ background-color: #f2f2f2; }}
|
|
|
|
| 125 |
@app.route('/convert', methods=['POST'])
|
| 126 |
def convert_endpoint():
|
| 127 |
data = request.json
|
| 128 |
+
temp_html_path = None
|
| 129 |
try:
|
| 130 |
full_html = build_full_html(
|
| 131 |
markdown_text=data.get('markdown_text', ''),
|
|
|
|
| 133 |
include_fontawesome=data.get('include_fontawesome', False)
|
| 134 |
)
|
| 135 |
|
| 136 |
+
# *** THIS IS THE ONLY CHANGE IN THIS FILE ***
|
| 137 |
+
# Add the 'xvfb' option to tell imgkit to use the virtual display.
|
| 138 |
+
options = {
|
| 139 |
+
"quiet": "",
|
| 140 |
+
'encoding': "UTF-8",
|
| 141 |
+
'xvfb': ''
|
| 142 |
+
}
|
| 143 |
|
|
|
|
| 144 |
temp_html_path = os.path.join(TEMP_DIR, f"{uuid.uuid4()}.html")
|
| 145 |
with open(temp_html_path, "w", encoding="utf-8") as f:
|
| 146 |
f.write(full_html)
|
|
|
|
| 150 |
if download_type == 'html':
|
| 151 |
return send_file(BytesIO(full_html.encode("utf-8")), as_attachment=True, download_name="output.html", mimetype="text/html")
|
| 152 |
else:
|
|
|
|
| 153 |
png_bytes = imgkit.from_file(temp_html_path, False, options=options)
|
| 154 |
return send_file(BytesIO(png_bytes), as_attachment=True, download_name="output.png", mimetype="image/png")
|
| 155 |
else:
|
|
|
|
| 156 |
png_bytes = imgkit.from_file(temp_html_path, False, options=options)
|
| 157 |
png_base64 = base64.b64encode(png_bytes).decode('utf-8')
|
| 158 |
return jsonify({'preview_html': full_html, 'preview_png_base64': png_base64})
|
|
|
|
| 161 |
traceback.print_exc()
|
| 162 |
return jsonify({'error': f'Failed to convert content: {str(e)}'}), 500
|
| 163 |
finally:
|
|
|
|
| 164 |
if temp_html_path and os.path.exists(temp_html_path):
|
| 165 |
os.remove(temp_html_path)
|
| 166 |
|
| 167 |
+
# --- MAIN PAGE RENDERER (Unchanged) ---
|
| 168 |
@app.route('/')
|
| 169 |
def index():
|
| 170 |
highlight_styles = sorted(list(get_all_styles()))
|
|
|
|
| 196 |
.component-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; }
|
| 197 |
.component-container { border: 1px solid #e0e0e0; border-radius: 5px; background: #fafafa; }
|
| 198 |
.component-header { background: #f1f1f1; padding: 8px 12px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; gap: 10px; }
|
| 199 |
+
.component-content textarea { height: 150px; }
|
| 200 |
.selection-controls { margin: 15px 0; display: flex; gap: 10px; }
|
| 201 |
</style>
|
| 202 |
</head>
|
| 203 |
<body>
|
| 204 |
<h1>Intelligent Markdown Converter</h1>
|
| 205 |
<form id="main-form" onsubmit="return false;">
|
|
|
|
| 206 |
<fieldset><legend>1. Load Content</legend><div id="info-box" class="info"></div><textarea id="markdown-text-input" name="markdown_text" rows="8"></textarea><div style="margin-top: 10px; display: flex; align-items: center; gap: 10px;"><label for="markdown-file-input">Or upload a file:</label><input type="file" id="markdown-file-input" name="markdown_file" accept=".md,.txt,text/markdown"></div><div style="margin-top: 15px;"><button type="button" id="load-btn" class="action-btn">Load & Analyze</button></div></fieldset>
|
| 207 |
<fieldset id="components-fieldset" style="display:none;"><legend>2. Select Components</legend><div class="selection-controls"><button type="button" onclick="toggleAllComponents(true)">Select All</button><button type="button" onclick="toggleAllComponents(false)">Deselect All</button></div><div id="components-container" class="component-grid"></div></fieldset>
|
| 208 |
<fieldset><legend>3. Configure Styles</legend><div class="style-grid"><div><label>Font Family:</label><select id="font_family"><optgroup label="Sans-Serif"><option value="'Arial', sans-serif">Arial</option><option value="'Roboto', sans-serif">Roboto</option></optgroup><optgroup label="Serif"><option value="'Times New Roman', serif">Times New Roman</option><option value="'Georgia', serif">Georgia</option></optgroup></select></div><div><label>Font Size (px):</label><input type="number" id="font_size" value="16"></div><div><label>Highlight Theme:</label><select id="highlight_theme"><option value="none">None</option>{% for style in highlight_styles %}<option value="{{ style }}" {% if style == 'default' %}selected{% endif %}>{{ style }}</option>{% endfor %}</select></div><div><label>Text Color:</label><input type="color" id="text_color" value="#333333"></div><div><label>Background Color:</label><input type="color" id="background_color" value="#ffffff"></div><div><label>Code Padding (px):</label><input type="number" id="code_padding" value="15"></div></div><div><input type="checkbox" id="include_fontawesome"><label for="include_fontawesome">Include Font Awesome</label></div><div><label for="custom_css">Custom CSS:</label><textarea id="custom_css" rows="3"></textarea></div></fieldset>
|
|
|
|
| 225 |
<div id="png-preview-container" class="preview-container"></div>
|
| 226 |
</div>
|
| 227 |
<script>
|
|
|
|
|
|
|
| 228 |
const loadBtn = document.getElementById('load-btn'), generateBtn = document.getElementById('generate-btn'),
|
| 229 |
downloadHtmlBtn = document.getElementById('download-html-btn'), downloadPngBtn = document.getElementById('download-png-btn'),
|
| 230 |
markdownTextInput = document.getElementById('markdown-text-input'), markdownFileInput = document.getElementById('markdown-file-input'),
|
|
|
|
| 254 |
include_fontawesome: document.getElementById('include_fontawesome').checked,
|
| 255 |
};
|
| 256 |
}
|
|
|
|
| 257 |
generateBtn.addEventListener('click', async () => {
|
| 258 |
generateBtn.textContent = 'Generating...'; generateBtn.disabled = true; errorBox.style.display = 'none';
|
| 259 |
const payload = buildPayload();
|
|
|
|
| 313 |
""", highlight_styles=highlight_styles)
|
| 314 |
|
| 315 |
if __name__ == "__main__":
|
|
|
|
|
|
|
| 316 |
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))
|