broadfield-dev commited on
Commit
4414c6c
·
verified ·
1 Parent(s): 83bea90

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +23 -26
app.py CHANGED
@@ -1,22 +1,22 @@
1
  from flask import Flask, request, render_template_string, send_file, jsonify
2
  import markdown
3
- import imgkit
4
  import os
5
  import traceback
6
  from io import BytesIO
7
  import re
8
  import base64
9
- from pygments import highlight
10
  from pygments.lexers import get_lexer_by_name
11
  from pygments.formatters import HtmlFormatter
12
  from pygments.styles import get_all_styles
 
 
13
 
14
  app = Flask(__name__)
15
 
16
- TEMP_DIR = os.path.join(os.getcwd(), "temp")
 
17
  os.makedirs(TEMP_DIR, exist_ok=True)
18
 
19
- # --- FORMAT PARSING AND DETECTION (Unchanged) ---
20
  def parse_repo2markdown(text):
21
  components = []
22
  pattern = re.compile(r'### File: (.*?)\n([\s\S]*?)(?=\n### File:|\Z)', re.MULTILINE)
@@ -55,7 +55,6 @@ def parse_changelog(text):
55
  components.append({'type': 'version', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()})
56
  return components
57
 
58
-
59
  @app.route('/parse', methods=['POST'])
60
  def parse_endpoint():
61
  text = request.form.get('markdown_text', '')
@@ -76,7 +75,6 @@ def parse_endpoint():
76
  except Exception as e:
77
  return jsonify({'error': f'Failed to parse: {e}'}), 500
78
 
79
- # --- HTML & PNG BUILDER (Unchanged but correct logic) ---
80
  def build_full_html(markdown_text, styles, include_fontawesome):
81
  wrapper_id = "#output-wrapper"
82
  font_family = styles.get('font_family', "'Arial', sans-serif")
@@ -96,8 +94,6 @@ def build_full_html(markdown_text, styles, include_fontawesome):
96
  font-family: {font_family}; font-size: {styles.get('font_size', '16')}px;
97
  color: {styles.get('text_color', '#333')}; background-color: {styles.get('background_color', '#fff')};
98
  }}
99
- /* ... other scoped styles ... */
100
-
101
  {wrapper_id} table {{ border-collapse: collapse; width: 100%; }}
102
  {wrapper_id} th, {wrapper_id} td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
103
  {wrapper_id} th {{ background-color: #f2f2f2; }}
@@ -116,13 +112,13 @@ def build_full_html(markdown_text, styles, include_fontawesome):
116
 
117
  full_html = f"""<!DOCTYPE html>
118
  <html><head><meta charset="UTF-8">{google_font_link}{fontawesome_link}<style>
119
- #ouput-wrapper {{ background-color: {styles.get('background_color', '#fff')}; padding: 25px; display: inline-block;}}
 
120
  {scoped_css}
121
  </style></head><body>{final_html_body}</body></html>"""
122
 
123
  return full_html
124
 
125
- # --- API ENDPOINT for Conversion (CHANGED) ---
126
  @app.route('/convert', methods=['POST'])
127
  def convert_endpoint():
128
  data = request.json
@@ -132,25 +128,34 @@ def convert_endpoint():
132
  styles=data.get('styles', {}),
133
  include_fontawesome=data.get('include_fontawesome', False)
134
  )
135
- # Define options here to avoid repetition and add the required fix
136
- options = {"quiet": "", 'encoding': "UTF-8", "--no-cache": ""}
137
-
 
 
 
 
 
 
 
 
 
 
 
 
138
  if data.get('download', False):
139
  download_type = data.get('download_type', 'png')
140
  if download_type == 'html':
141
  return send_file(BytesIO(full_html.encode("utf-8")), as_attachment=True, download_name="output.html", mimetype="text/html")
142
  else:
143
- png_bytes = imgkit.from_string(full_html, False, options=options)
144
  return send_file(BytesIO(png_bytes), as_attachment=True, download_name="output.png", mimetype="image/png")
145
  else:
146
- png_bytes = imgkit.from_string(full_html, False, options=options)
147
  png_base64 = base64.b64encode(png_bytes).decode('utf-8')
148
  return jsonify({'preview_html': full_html, 'preview_png_base64': png_base64})
149
  except Exception as e:
150
  traceback.print_exc()
151
  return jsonify({'error': f'Failed to convert content: {str(e)}'}), 500
152
 
153
- # --- MAIN PAGE RENDERER (with corrected CSS) ---
154
  @app.route('/')
155
  def index():
156
  highlight_styles = sorted(list(get_all_styles()))
@@ -182,22 +187,19 @@ def index():
182
  .component-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; }
183
  .component-container { border: 1px solid #e0e0e0; border-radius: 5px; background: #fafafa; }
184
  .component-header { background: #f1f1f1; padding: 8px 12px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; gap: 10px; }
185
- .component-content textarea { height: 150px; } /* <-- FIXED: Taller text area */
186
  .selection-controls { margin: 15px 0; display: flex; gap: 10px; }
187
  </style>
188
  </head>
189
  <body>
190
  <h1>Intelligent Markdown Converter</h1>
191
  <form id="main-form" onsubmit="return false;">
192
- <!-- Input and Styling sections -->
193
  <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>
194
  <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>
195
  <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>
196
  <div class="main-actions"><button type="button" id="generate-btn" class="generate-btn">Generate Preview</button></div>
197
  </form>
198
-
199
  <div id="error-box" class="error"></div>
200
-
201
  <div id="preview-section" style="display:none;">
202
  <h2>Preview</h2>
203
  <div class="preview-header">
@@ -212,8 +214,6 @@ def index():
212
  <div id="png-preview-container" class="preview-container"></div>
213
  </div>
214
  <script>
215
- // --- All JavaScript is unchanged from the previous correct version ---
216
- // It correctly gathers style info without modifying the parent page.
217
  const loadBtn = document.getElementById('load-btn'), generateBtn = document.getElementById('generate-btn'),
218
  downloadHtmlBtn = document.getElementById('download-html-btn'), downloadPngBtn = document.getElementById('download-png-btn'),
219
  markdownTextInput = document.getElementById('markdown-text-input'), markdownFileInput = document.getElementById('markdown-file-input'),
@@ -243,7 +243,6 @@ def index():
243
  include_fontawesome: document.getElementById('include_fontawesome').checked,
244
  };
245
  }
246
- loadBtn.addEventListener('click', async () => { /* Logic unchanged */ });
247
  generateBtn.addEventListener('click', async () => {
248
  generateBtn.textContent = 'Generating...'; generateBtn.disabled = true; errorBox.style.display = 'none';
249
  const payload = buildPayload();
@@ -303,6 +302,4 @@ def index():
303
  """, highlight_styles=highlight_styles)
304
 
305
  if __name__ == "__main__":
306
- # Ensure you have installed the required libraries:
307
- # pip install Flask markdown imgkit pygments
308
- app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))
 
1
  from flask import Flask, request, render_template_string, send_file, jsonify
2
  import markdown
 
3
  import os
4
  import traceback
5
  from io import BytesIO
6
  import re
7
  import base64
 
8
  from pygments.lexers import get_lexer_by_name
9
  from pygments.formatters import HtmlFormatter
10
  from pygments.styles import get_all_styles
11
+ # New import for the headless browser
12
+ from playwright.sync_api import sync_playwright
13
 
14
  app = Flask(__name__)
15
 
16
+ # Corrected line to use the universally writable /tmp directory
17
+ TEMP_DIR = "/tmp/markdown_temp"
18
  os.makedirs(TEMP_DIR, exist_ok=True)
19
 
 
20
  def parse_repo2markdown(text):
21
  components = []
22
  pattern = re.compile(r'### File: (.*?)\n([\s\S]*?)(?=\n### File:|\Z)', re.MULTILINE)
 
55
  components.append({'type': 'version', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()})
56
  return components
57
 
 
58
  @app.route('/parse', methods=['POST'])
59
  def parse_endpoint():
60
  text = request.form.get('markdown_text', '')
 
75
  except Exception as e:
76
  return jsonify({'error': f'Failed to parse: {e}'}), 500
77
 
 
78
  def build_full_html(markdown_text, styles, include_fontawesome):
79
  wrapper_id = "#output-wrapper"
80
  font_family = styles.get('font_family', "'Arial', sans-serif")
 
94
  font-family: {font_family}; font-size: {styles.get('font_size', '16')}px;
95
  color: {styles.get('text_color', '#333')}; background-color: {styles.get('background_color', '#fff')};
96
  }}
 
 
97
  {wrapper_id} table {{ border-collapse: collapse; width: 100%; }}
98
  {wrapper_id} th, {wrapper_id} td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
99
  {wrapper_id} th {{ background-color: #f2f2f2; }}
 
112
 
113
  full_html = f"""<!DOCTYPE html>
114
  <html><head><meta charset="UTF-8">{google_font_link}{fontawesome_link}<style>
115
+ body {{ background-color: transparent; margin: 0; padding: 0; }}
116
+ #output-wrapper {{ background-color: {styles.get('background_color', '#fff')}; padding: 25px; display: inline-block;}}
117
  {scoped_css}
118
  </style></head><body>{final_html_body}</body></html>"""
119
 
120
  return full_html
121
 
 
122
  @app.route('/convert', methods=['POST'])
123
  def convert_endpoint():
124
  data = request.json
 
128
  styles=data.get('styles', {}),
129
  include_fontawesome=data.get('include_fontawesome', False)
130
  )
131
+
132
+ # --- REPLACEMENT FOR IMGKIT ---
133
+ with sync_playwright() as p:
134
+ browser = p.chromium.launch()
135
+ page = browser.new_page()
136
+ page.set_content(full_html)
137
+
138
+ # Locate the specific element we want to capture
139
+ element = page.locator("#output-wrapper")
140
+
141
+ # Take a screenshot of just that element
142
+ png_bytes = element.screenshot(type="png")
143
+ browser.close()
144
+ # --- END REPLACEMENT ---
145
+
146
  if data.get('download', False):
147
  download_type = data.get('download_type', 'png')
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
  return send_file(BytesIO(png_bytes), as_attachment=True, download_name="output.png", mimetype="image/png")
152
  else:
 
153
  png_base64 = base64.b64encode(png_bytes).decode('utf-8')
154
  return jsonify({'preview_html': full_html, 'preview_png_base64': png_base64})
155
  except Exception as e:
156
  traceback.print_exc()
157
  return jsonify({'error': f'Failed to convert content: {str(e)}'}), 500
158
 
 
159
  @app.route('/')
160
  def index():
161
  highlight_styles = sorted(list(get_all_styles()))
 
187
  .component-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; }
188
  .component-container { border: 1px solid #e0e0e0; border-radius: 5px; background: #fafafa; }
189
  .component-header { background: #f1f1f1; padding: 8px 12px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; gap: 10px; }
190
+ .component-content textarea { height: 150px; }
191
  .selection-controls { margin: 15px 0; display: flex; gap: 10px; }
192
  </style>
193
  </head>
194
  <body>
195
  <h1>Intelligent Markdown Converter</h1>
196
  <form id="main-form" onsubmit="return false;">
 
197
  <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>
198
  <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>
199
  <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>
200
  <div class="main-actions"><button type="button" id="generate-btn" class="generate-btn">Generate Preview</button></div>
201
  </form>
 
202
  <div id="error-box" class="error"></div>
 
203
  <div id="preview-section" style="display:none;">
204
  <h2>Preview</h2>
205
  <div class="preview-header">
 
214
  <div id="png-preview-container" class="preview-container"></div>
215
  </div>
216
  <script>
 
 
217
  const loadBtn = document.getElementById('load-btn'), generateBtn = document.getElementById('generate-btn'),
218
  downloadHtmlBtn = document.getElementById('download-html-btn'), downloadPngBtn = document.getElementById('download-png-btn'),
219
  markdownTextInput = document.getElementById('markdown-text-input'), markdownFileInput = document.getElementById('markdown-file-input'),
 
243
  include_fontawesome: document.getElementById('include_fontawesome').checked,
244
  };
245
  }
 
246
  generateBtn.addEventListener('click', async () => {
247
  generateBtn.textContent = 'Generating...'; generateBtn.disabled = true; errorBox.style.display = 'none';
248
  const payload = buildPayload();
 
302
  """, highlight_styles=highlight_styles)
303
 
304
  if __name__ == "__main__":
305
+ app.run(host="0.0.0.0", port=7860)