aradhyapavan commited on
Commit
b58f685
·
verified ·
1 Parent(s): f13a2a6

Readme Editor

Browse files
app.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import os
5
+ from flask import Flask, render_template, request, send_file, jsonify
6
+
7
+ app = Flask(__name__)
8
+
9
+
10
+ @app.get("/")
11
+ def index():
12
+ return render_template("index.html")
13
+
14
+
15
+ @app.post("/export/md")
16
+ def export_md():
17
+ content = request.json.get("markdown", "")
18
+ buf = io.BytesIO(content.encode("utf-8"))
19
+ return send_file(buf, as_attachment=True, download_name="document.md", mimetype="text/markdown")
20
+
21
+
22
+ @app.post("/export/html")
23
+ def export_html():
24
+ html = request.json.get("html", "")
25
+ with_styles = bool(request.args.get("withStyles", "1") not in ("0", "false", "False"))
26
+ if with_styles:
27
+ wrapped = f"""<!DOCTYPE html><html><head>
28
+ <meta charset='utf-8'>
29
+ <meta name='viewport' content='width=device-width, initial-scale=1'>
30
+ <link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css' rel='stylesheet'>
31
+ <link href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css' rel='stylesheet'>
32
+ <link href='https://unpkg.com/@primer/css@21.0.7/dist/primer.css' rel='stylesheet'>
33
+ <style> body{{padding:2rem}} </style>
34
+ </head><body class='markdown-body'>{html}</body></html>"""
35
+ else:
36
+ wrapped = html
37
+
38
+ buf = io.BytesIO(wrapped.encode("utf-8"))
39
+ return send_file(buf, as_attachment=True, download_name="document.html", mimetype="text/html")
40
+
41
+
42
+ @app.post("/import/html")
43
+ def import_html():
44
+ file = request.files.get("file")
45
+ if not file:
46
+ return jsonify({"error": "No file"}), 400
47
+ text = file.read().decode("utf-8", errors="ignore")
48
+ return jsonify({"html": text})
49
+
50
+
51
+ @app.post("/import/md")
52
+ def import_md():
53
+ file = request.files.get("file")
54
+ if not file:
55
+ return jsonify({"error": "No file"}), 400
56
+ text = file.read().decode("utf-8", errors="ignore")
57
+ return jsonify({"markdown": text})
58
+
59
+
60
+ if __name__ == "__main__":
61
+ port = int(os.environ.get("PORT", "7860"))
62
+ host = os.environ.get("HOST", "0.0.0.0")
63
+ app.run(host=host, port=port)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Flask==3.0.3
2
+ gunicorn==23.0.0
static/favicon/android-chrome-192x192.png ADDED
static/favicon/android-chrome-512x512.png ADDED
static/favicon/apple-touch-icon.png ADDED
static/favicon/favicon-16x16.png ADDED
static/favicon/favicon-32x32.png ADDED
static/favicon/favicon.ico ADDED
static/favicon/site.webmanifest ADDED
@@ -0,0 +1 @@
 
 
1
+ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
static/main.js ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ let easyMDE;
3
+ const previewEl = document.getElementById('preview');
4
+ const hljsTheme = document.getElementById('hljs-theme');
5
+ const docTitlePill = document.getElementById('doc-title-pill');
6
+
7
+
8
+ marked.setOptions({
9
+ breaks: true,
10
+ highlight: (code, lang) => {
11
+ try { return hljs.highlight(code, {language: lang}).value; }
12
+ catch { return hljs.highlightAuto(code).value; }
13
+ }
14
+ });
15
+
16
+ function renderPreview(md) {
17
+ const html = marked.parse(md || '');
18
+ previewEl.innerHTML = `<div class="markdown-body container">${html}</div>`;
19
+
20
+ try { if (window.renderMathInElement) renderMathInElement(previewEl, { delimiters: [
21
+ {left: '$$', right: '$$', display: true},
22
+ {left: '$', right: '$', display: false},
23
+ {left: '\\(', right: '\\)', display: false},
24
+ {left: '\\[', right: '\\]', display: true}
25
+ ]}); } catch {}
26
+
27
+ try {
28
+ if (window.mermaid && !window.__MERMAID_INIT__) {
29
+ window.__MERMAID_INIT__ = true;
30
+ mermaid.initialize({ startOnLoad: false, theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default' });
31
+ }
32
+ const blocks = previewEl.querySelectorAll('pre code.language-mermaid, code.language-mermaid');
33
+ blocks.forEach((node, i) => {
34
+ const src = node.textContent.trim();
35
+ const container = document.createElement('div');
36
+ container.className = 'mermaid';
37
+ container.textContent = src;
38
+ const pre = node.closest('pre');
39
+ if (pre) pre.replaceWith(container); else node.replaceWith(container);
40
+ });
41
+ if (window.mermaid && previewEl.querySelector('.mermaid')) mermaid.run({ querySelector: '.mermaid' });
42
+ } catch {}
43
+ }
44
+
45
+ function getMarkdown() { return easyMDE ? easyMDE.value() : ''; }
46
+
47
+ function setupEditor() {
48
+ easyMDE = new EasyMDE({
49
+ element: document.getElementById('editor'),
50
+ spellChecker: false,
51
+ autosave: { enabled: true, uniqueId: 'md-editor-autosave', delay: 1000 },
52
+ previewRender: (plainText) => marked.parse(plainText),
53
+ toolbar: [
54
+ { name: 'open', action: () => document.getElementById('btn-import-md').click(), className: 'fa fa-folder-open', title: 'Open Markdown file' },
55
+ 'undo', 'redo', '|',
56
+ 'bold', 'italic', 'heading-1', 'heading-2', 'heading-3', 'table', '|',
57
+ { name: 'indent', action: (ed) => ed.codemirror.execCommand('indentMore'), className: 'fa fa-indent', title: 'Indent' },
58
+ { name: 'outdent', action: (ed) => ed.codemirror.execCommand('indentLess'), className: 'fa fa-outdent', title: 'Outdent' },
59
+ '|', 'unordered-list', 'ordered-list', '|', 'quote', 'code', 'horizontal-rule', '|', 'link', 'image', '|',
60
+ 'preview', 'side-by-side', 'fullscreen', '|',
61
+ { name: 'clear', action: () => { easyMDE.value(''); renderPreview(''); }, className: 'fa fa-eraser', title: 'Clear' }
62
+ ],
63
+ });
64
+ easyMDE.codemirror.on('change', () => renderPreview(getMarkdown()));
65
+
66
+
67
+ const initial = getMarkdown();
68
+ if (!initial || initial.trim() === '') {
69
+ const sample = `# Welcome to Markdown Editor!\n\nThis page works like **StackEdit**. Edit on the left, preview on the right.\nIf you want to learn Markdown quickly, read and modify this document.\n\n# Files\n\nYour content is autosaved in your browser and works **offline**.\n\n## Create files and folders\nUse the Samples menu for starters or begin typing.\n\n## Switch to another file\nYou can paste any Markdown content here and it will render instantly.\n\n## Export a file\nUse the Export menu to save as Markdown, raw HTML, styled HTML, or PDF.\n\n# Synchronization\n\nThis demo does not connect to cloud storage, but you can copy/paste to any service.\n\n# Markdown extensions\n\n## Code blocks\n\n\`\`\`python\nprint('Hello, Markdown!')\n\`\`\`\n\n## Tables\n\n| Feature | Status |\n|---|---|\n| Autosave | ✅ |\n| Dark Mode | ✅ |\n| Export | ✅ |\n\n## Blockquotes\n\n> Tips: Use the toolbar to format text, add lists, tables, and more.\n\n## Mermaid diagrams\n\n\`\`\`mermaid\ngraph LR\nA[Write] --> B[Preview]\nB --> C{Export}\nC -->|PDF| D[Share]\nC -->|HTML| E[Publish]\n\`\`\`\n\n---\n\nHappy writing!`;
70
+ easyMDE.value(sample);
71
+ }
72
+ renderPreview(getMarkdown());
73
+ }
74
+
75
+ function setupSplit() {
76
+ if (window.Split) {
77
+ Split(['#leftPane', '#rightPane'], { sizes: [50, 50], minSize: 280, gutterSize: 8, cursor: 'col-resize' });
78
+ }
79
+ }
80
+
81
+
82
+ function pickFile(inputEl, cb) {
83
+ inputEl.onchange = (e) => { const f = e.target.files[0]; if (f) cb(f); inputEl.value = ''; };
84
+ inputEl.click();
85
+ }
86
+
87
+ function readFileText(file) {
88
+ return new Promise((resolve) => {
89
+ const reader = new FileReader();
90
+ reader.onload = (e) => resolve(e.target.result);
91
+ reader.readAsText(file);
92
+ });
93
+ }
94
+
95
+
96
+ function bindActions() {
97
+ // Theme toggle
98
+ const themeBtn = document.getElementById('btn-theme');
99
+ const setDark = (on) => {
100
+ document.documentElement.classList.toggle('light', !on);
101
+ document.documentElement.classList.toggle('dark', !!on);
102
+
103
+ if (hljsTheme) {
104
+ hljsTheme.href = on
105
+ ? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css'
106
+ : 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css';
107
+ }
108
+ try { localStorage.setItem('md-theme-dark', on ? '1' : '0'); } catch {}
109
+ };
110
+ try {
111
+ const saved = localStorage.getItem('md-theme-dark');
112
+ const initialDark = saved === null ? true : saved === '1';
113
+ setDark(initialDark);
114
+ const icon = themeBtn.querySelector('i');
115
+ if (icon) icon.className = initialDark ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
116
+ } catch { setDark(true); }
117
+ themeBtn.addEventListener('click', () => {
118
+ const isDark = document.documentElement.classList.contains('dark');
119
+ const nowDark = !isDark;
120
+ setDark(nowDark);
121
+ const icon = themeBtn.querySelector('i');
122
+ if (icon) icon.className = nowDark ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
123
+ });
124
+
125
+
126
+ document.getElementById('btn-import-md').addEventListener('click', () => {
127
+ pickFile(document.getElementById('file-md'), async (file) => {
128
+ const text = await readFileText(file);
129
+ easyMDE.value(text);
130
+ renderPreview(text);
131
+ });
132
+ });
133
+
134
+
135
+ document.getElementById('btn-import-html').addEventListener('click', () => {
136
+ pickFile(document.getElementById('file-html'), async (file) => {
137
+ const text = await readFileText(file);
138
+ easyMDE.value(text);
139
+ renderPreview(text);
140
+ });
141
+ });
142
+
143
+
144
+ const samplesBtn = document.getElementById('btn-samples');
145
+ const samplesModal = new bootstrap.Modal(document.getElementById('samplesModal'));
146
+ samplesBtn.addEventListener('click', () => samplesModal.show());
147
+
148
+ const samples = {
149
+ cheatsheet: `# Markdown Cheat Sheet\n\n## Headings\n# H1\n## H2\n### H3\n\n## Emphasis\n*italic* **bold** ~~strike~~ \`code\`\n\n## Lists\n- unordered\n- list\n - nested\n1. ordered\n2. list\n\n## Links & Images\n[OpenAI](https://openai.com) \\n![Image](https://via.placeholder.com/200x80.png?text=Image)\n\n## Blockquote\n> A wise quote.\n\n## Table\n| Name | Role |\n|---|---|\n| Alice | Developer |\n| Bob | Designer |\n\n## Task List\n- [x] design\n- [ ] implement\n- [ ] test\n\n## Code Block\n\`\`\`python\nfrom math import sqrt\nprint(sqrt(9))\n\`\`\`\n\n---\n\n### Horizontal rule above`,
150
+ email: `**Subject:** Request to Schedule Project Kickoff Meeting\n\nDear [Recipient Name],\n\nI hope you're doing well. I'd like to schedule a 30‑minute kickoff meeting to align on [Project/Topic].\n\n**Proposed agenda:**\n- Objectives and success criteria\n- Scope, timeline, and roles\n- Risks and dependencies\n- Next steps\n\n**Suggested times (IST):**\n- [Option 1, e.g., Tue 3:00–3:30 PM]\n- [Option 2, e.g., Wed 11:00–11:30 AM]\n\nIf these don't work, please share alternative slots and your preferred meeting platform.\n\nThanks in advance, and I look forward to our discussion.\n\nBest regards,\n\n[Your Full Name]\n[Your Role], [Your Company]\nPhone: [Your Phone]\nEmail: you@example.com\n\n---`,
151
+ readme: `# Project Title\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![Built with](https://img.shields.io/badge/Built%20with-Bootstrap%205-blue)\n\nA short description of your project.\n\n## Features\n- Awesome feature\n- Fast and reliable\n\n## Installation\n\`\`\`bash\npip install -r requirements.txt\npython app.py\n\`\`\`\n\n## Usage\nDescribe how to use it.\n\n## License\nMIT © You`,
152
+ github_readme_pro: `# Awesome App\n\n[![CI](https://github.com/OWNER/REPO/actions/workflows/ci.yml/badge.svg)](https://github.com/OWNER/REPO/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n> One‑liner that explains what this project does.\n\n## Table of Contents\n- [Demo](#demo)\n- [Stack](#stack)\n- [Install](#install)\n- [Project Structure](#project-structure)\n- [CLI](#cli)\n- [Docs](#docs)\n\n## Demo\n![screenshot](https://via.placeholder.com/960x320.png?text=Screenshot)\n\n## Stack\n| Layer | Tech |\n|---|---|\n| Frontend | React / Vite |\n| Backend | FastAPI |\n| DB | PostgreSQL |\n| Infra | Docker / GH Actions |\n\n## Install\n\`\`\`bash\n git clone https://github.com/OWNER/REPO && cd REPO\n cp .env.example .env\n docker compose up -d --build\n\`\`\`\n\n## Project Structure\n\`\`\`text\nREPO/\n ├─ apps/\n │ ├─ api/\n │ └─ web/\n ├─ packages/\n │ ├─ ui/\n │ └─ config/\n └─ tools/\n └─ scripts/\n\`\`\`\n\n## Docs\n### Data Flow (Mermaid)\n\`\`\`mermaid\ngraph TD\nA[Browser] --> B(Edge API)\nB --> C{Auth}\nC -->|pass| D[Service] --> E[(DB)]\nC -->|fail| F[401]\n\`\`\`\n\n## License\nMIT`,
153
+ math_notes: `# KaTeX Quick Examples\n\nInline: $E=mc^2$, $\\int_0^1 x^2\\,dx = \\tfrac{1}{3}$.\n\nBlock:\n\n$$\\sum_{i=1}^n i = \\frac{n(n+1)}{2}$$\n\nEuler integral for the gamma function:\n\n$$\\Gamma(z)=\\int_0^\\infty t^{z-1}e^{-t}\\,dt$$\n\n> Tip: See more syntax in the MathJax guide.`,
154
+ api_docs: `# HTTP API\n\n> Version 1\n\n## Authentication\nUse a Bearer token in the Authorization header.\n\n## Endpoints\n### GET /v1/users\nReturns a paginated list of users.\n\n\`\`\`http\nGET /v1/users?page=1&pageSize=20\nAuthorization: Bearer <token>\n\`\`\`\n\nResponse:\n\`\`\`json\n{\n "items": [{"id": 1, "name": "Ada"}],\n "nextPage": 2\n}\n\`\`\`\n\n### Errors\n| Code | Meaning |\n|---|---|\n| 400 | Bad request |\n| 401 | Unauthorized |\n| 404 | Not found |`,
155
+ meeting_notes: `# Meeting Notes\n\n**When**: 2025-08-28 14:00–14:45 IST \n**Where**: Zoom \n**Attendees**: Alex, Priya, Sam\n\n## Agenda\n1. Status updates\n2. Release checklist\n3. Risks\n\n## Decisions\n- Ship v1.2 on Monday\n- Freeze new features until post‑release\n\n## Action Items\n- [ ] Alex: finalize docs\n- [ ] Priya: run load tests\n- [ ] Sam: prepare rollout plan\n\n## Timeline\n\`\`\`mermaid\ngantt\n title Release plan\n dateFormat YYYY-MM-DD\n section Prep\n Docs :a1, 2025-08-28, 2d\n Load tests :a2, 2025-08-28, 2d\n section Ship\n Release :milestone, 2025-08-30, 1d\n\`\`\``
156
+ };
157
+
158
+ document.querySelectorAll('[data-sample]').forEach(btn => {
159
+ btn.addEventListener('click', () => {
160
+ const key = btn.getAttribute('data-sample');
161
+ easyMDE.value(samples[key] || '');
162
+ renderPreview(getMarkdown());
163
+ samplesModal.hide();
164
+ });
165
+ });
166
+ document.querySelectorAll('[data-sample-append]').forEach(btn => {
167
+ btn.addEventListener('click', () => {
168
+ const key = btn.getAttribute('data-sample-append');
169
+ const text = samples[key] || '';
170
+ easyMDE.value((getMarkdown() + '\n\n' + text).trim());
171
+ renderPreview(getMarkdown());
172
+ samplesModal.hide();
173
+ });
174
+ });
175
+
176
+
177
+ document.getElementById('btn-export-md').addEventListener('click', () => {
178
+ const content = easyMDE.value();
179
+ const filename = document.getElementById('doc-title-pill').textContent || 'document';
180
+ const blob = new Blob([content], { type: 'text/markdown' });
181
+ saveAs(blob, `${filename}.md`);
182
+ });
183
+
184
+ document.getElementById('btn-export-html').addEventListener('click', () => {
185
+ const content = easyMDE.value();
186
+ const html = marked.parse(content);
187
+ const filename = document.getElementById('doc-title-pill').textContent || 'document';
188
+ const blob = new Blob([html], { type: 'text/html' });
189
+ saveAs(blob, `${filename}.html`);
190
+ });
191
+
192
+ document.getElementById('btn-export-html-styled').addEventListener('click', () => {
193
+ const content = easyMDE.value();
194
+ const html = marked.parse(content);
195
+ const styledHtml = `
196
+ <!DOCTYPE html>
197
+ <html lang="en">
198
+ <head>
199
+ <meta charset="UTF-8">
200
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
201
+ <title>${document.getElementById('doc-title-pill').textContent || 'Document'}</title>
202
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@primer/css@21.0.7/dist/primer.css">
203
+ <style>
204
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 2rem; }
205
+ .markdown-body { color: #24292f; }
206
+ .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { color: #24292f; margin-top: 1.5rem; }
207
+ .markdown-body table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
208
+ .markdown-body th, .markdown-body td { border: 1px solid #d0d7de; padding: 0.5rem; text-align: left; }
209
+ .markdown-body th { background-color: #f6f8fa; font-weight: 600; }
210
+ .markdown-body tr:nth-child(even) td { background-color: #fafbfc; }
211
+ .markdown-body pre { background-color: #f6f8fa; border: 1px solid #d0d7de; border-radius: 6px; padding: 1rem; overflow-x: auto; }
212
+ .markdown-body code { background-color: #f6f8fa; padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
213
+ .markdown-body blockquote { border-left: 4px solid #d0d7de; margin: 1rem 0; padding: 0 1rem; color: #6a737d; }
214
+ </style>
215
+ </head>
216
+ <body>
217
+ <div class="markdown-body">
218
+ ${html}
219
+ </div>
220
+ </body>
221
+ </html>`;
222
+ const filename = document.getElementById('doc-title-pill').textContent || 'document';
223
+ const blob = new Blob([styledHtml], { type: 'text/html' });
224
+ saveAs(blob, `${filename}-styled.html`);
225
+ });
226
+
227
+
228
+ document.getElementById('btn-export-pdf').addEventListener('click', () => {
229
+ const pdfOptionsModal = new bootstrap.Modal(document.getElementById('pdfOptionsModal'));
230
+ pdfOptionsModal.show();
231
+ });
232
+
233
+
234
+ document.getElementById('btn-generate-pdf').addEventListener('click', () => {
235
+ const orientation = document.querySelector('input[name="orientation"]:checked').value;
236
+ const paperSize = document.getElementById('paperSize').value;
237
+
238
+
239
+ const pdfOptionsModal = bootstrap.Modal.getInstance(document.getElementById('pdfOptionsModal'));
240
+ pdfOptionsModal.hide();
241
+
242
+
243
+ generatePDF(orientation, paperSize);
244
+ });
245
+
246
+ function generatePDF(orientation, paperSize, fullPage) {
247
+ const content = easyMDE.value();
248
+ const html = marked.parse(content);
249
+
250
+
251
+ const pageBreakStyles = fullPage ? '' : `
252
+ .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
253
+ page-break-after: avoid;
254
+ }
255
+ .markdown-body table {
256
+ page-break-inside: avoid;
257
+ }
258
+ .markdown-body pre {
259
+ page-break-inside: avoid;
260
+ }
261
+ .markdown-body hr {
262
+ page-break-after: avoid;
263
+ }`;
264
+
265
+
266
+ const styledHtml = `
267
+ <!DOCTYPE html>
268
+ <html lang="en">
269
+ <head>
270
+ <meta charset="UTF-8">
271
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
272
+ <title>${document.getElementById('doc-title-pill').textContent || 'Document'}</title>
273
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@primer/css@21.0.7/dist/primer.css">
274
+ <style>
275
+ /* Force light theme for PDF */
276
+ body {
277
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
278
+ line-height: 1.6;
279
+ margin: 0;
280
+ padding: 1rem;
281
+ color: #24292f !important;
282
+ background: #ffffff !important;
283
+ }
284
+ .markdown-body {
285
+ color: #24292f !important;
286
+ background: #ffffff !important;
287
+ }
288
+ .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
289
+ color: #24292f !important;
290
+ margin-top: 1.5rem;
291
+ margin-bottom: 1rem;
292
+ }
293
+ .markdown-body p {
294
+ color: #24292f !important;
295
+ margin-bottom: 1rem;
296
+ }
297
+ .markdown-body ul, .markdown-body ol {
298
+ color: #24292f !important;
299
+ margin-bottom: 1rem;
300
+ }
301
+ .markdown-body li {
302
+ color: #24292f !important;
303
+ margin-bottom: 0.5rem;
304
+ }
305
+ .markdown-body table {
306
+ border-collapse: collapse;
307
+ width: 100%;
308
+ margin: 1rem 0;
309
+ background: #ffffff !important;
310
+ }
311
+ .markdown-body th, .markdown-body td {
312
+ border: 1px solid #d0d7de;
313
+ padding: 0.5rem;
314
+ text-align: left;
315
+ color: #24292f !important;
316
+ background: #ffffff !important;
317
+ }
318
+ .markdown-body th {
319
+ background-color: #f6f8fa !important;
320
+ color: #24292f !important;
321
+ font-weight: 600;
322
+ }
323
+ .markdown-body tr:nth-child(even) td {
324
+ background-color: #fafbfc !important;
325
+ color: #24292f !important;
326
+ }
327
+ .markdown-body pre {
328
+ background-color: #f6f8fa !important;
329
+ border: 1px solid #d0d7de;
330
+ border-radius: 6px;
331
+ padding: 1rem;
332
+ overflow-x: auto;
333
+ color: #24292f !important;
334
+ }
335
+ .markdown-body code {
336
+ background-color: #f6f8fa !important;
337
+ padding: 0.2rem 0.4rem;
338
+ border-radius: 3px;
339
+ font-size: 0.9em;
340
+ color: #24292f !important;
341
+ }
342
+ .markdown-body blockquote {
343
+ border-left: 4px solid #d0d7de;
344
+ margin: 1rem 0;
345
+ padding: 0 1rem;
346
+ color: #6a737d !important;
347
+ background-color: #f6f8fa !important;
348
+ }
349
+ .markdown-body img {
350
+ max-width: 100%;
351
+ height: auto;
352
+ border: 1px solid #d0d7de;
353
+ }
354
+ .markdown-body a {
355
+ color: #0969da !important;
356
+ text-decoration: none;
357
+ }
358
+ .markdown-body a:hover {
359
+ text-decoration: underline;
360
+ }
361
+ .markdown-body hr {
362
+ border: 0;
363
+ border-top: 1px solid #d0d7de;
364
+ margin: 1.5rem 0;
365
+ }
366
+ /* Override any dark theme styles */
367
+ * { color: inherit !important; }
368
+ ${pageBreakStyles}
369
+ </style>
370
+ </head>
371
+ <body>
372
+ <div class="markdown-body">
373
+ ${html}
374
+ </div>
375
+ </body>
376
+ </html>`;
377
+
378
+ const filename = document.getElementById('doc-title-pill').textContent || 'document';
379
+
380
+ if (fullPage) {
381
+
382
+ const tempDiv = document.createElement('div');
383
+ tempDiv.innerHTML = styledHtml;
384
+ tempDiv.style.position = 'absolute';
385
+ tempDiv.style.left = '-9999px';
386
+ tempDiv.style.top = '0';
387
+ tempDiv.style.width = '800px';
388
+ tempDiv.style.background = '#ffffff';
389
+ tempDiv.style.padding = '20px';
390
+ tempDiv.style.overflow = 'visible';
391
+ tempDiv.style.fontSize = '14px';
392
+ tempDiv.style.lineHeight = '1.6';
393
+ document.body.appendChild(tempDiv);
394
+
395
+
396
+ setTimeout(() => {
397
+ const contentHeight = Math.max(tempDiv.scrollHeight, tempDiv.offsetHeight);
398
+ console.log('Content height:', contentHeight);
399
+
400
+
401
+ document.body.removeChild(tempDiv);
402
+
403
+
404
+ const opt = {
405
+ margin: [0, 0, 0, 0],
406
+ filename: `${filename}-fullpage.pdf`,
407
+ image: { type: 'jpeg', quality: 0.98 },
408
+ html2canvas: {
409
+ scale: 1,
410
+ useCORS: true,
411
+ letterRendering: true,
412
+ backgroundColor: '#ffffff',
413
+ width: 800,
414
+ height: contentHeight,
415
+ scrollY: 0,
416
+ scrollX: 0,
417
+ allowTaint: true,
418
+ foreignObjectRendering: true
419
+ },
420
+ jsPDF: {
421
+ unit: 'px',
422
+ format: [800, contentHeight + 40],
423
+ orientation: 'portrait',
424
+ compress: true
425
+ }
426
+ };
427
+
428
+
429
+ html2pdf().from(styledHtml).set(opt).save();
430
+ }, 300);
431
+ } else {
432
+
433
+ const opt = {
434
+ margin: [10, 10, 10, 10],
435
+ filename: `${filename}.pdf`,
436
+ image: { type: 'jpeg', quality: 0.98 },
437
+ html2canvas: {
438
+ scale: 2,
439
+ useCORS: true,
440
+ letterRendering: true,
441
+ backgroundColor: '#ffffff'
442
+ },
443
+ jsPDF: {
444
+ unit: 'mm',
445
+ format: paperSize,
446
+ orientation: orientation
447
+ }
448
+ };
449
+
450
+ html2pdf().from(styledHtml).set(opt).save();
451
+ }
452
+ }
453
+
454
+
455
+ const readBtn = document.getElementById('btn-readmode');
456
+ if (readBtn) {
457
+ let readMode = false;
458
+ readBtn.addEventListener('click', () => {
459
+ readMode = !readMode;
460
+ document.body.classList.toggle('read-mode', readMode);
461
+ const icon = readBtn.querySelector('i');
462
+ if (icon) icon.className = readMode ? 'fa-solid fa-book' : 'fa-solid fa-book-open';
463
+ readBtn.textContent = readMode ? ' Exit read mode' : ' Read mode';
464
+ readBtn.prepend(icon);
465
+ });
466
+ }
467
+
468
+
469
+ if (docTitlePill) {
470
+ const savedTitle = localStorage.getItem('md-doc-title') || 'Welcome file';
471
+ docTitlePill.textContent = savedTitle;
472
+ docTitlePill.addEventListener('input', () => {
473
+ const v = (docTitlePill.textContent || '').trim() || 'Untitled';
474
+ localStorage.setItem('md-doc-title', v);
475
+ });
476
+ }
477
+ }
478
+
479
+ window.addEventListener('DOMContentLoaded', () => {
480
+
481
+ const ensureCss = (href) => { if (!document.querySelector(`link[href="${href}"]`)) { const l=document.createElement('link'); l.rel='stylesheet'; l.href=href; document.head.appendChild(l);} };
482
+ const ensureScript = (src, cb) => { if (document.querySelector(`script[src="${src}"]`)) { cb && cb(); return; } const s=document.createElement('script'); s.src=src; s.defer=true; s.onload=() => cb && cb(); document.body.appendChild(s); };
483
+
484
+ ensureCss('https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css');
485
+ ensureScript('https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js', () => {
486
+ ensureScript('https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js', () => {
487
+ try { renderPreview(getMarkdown()); } catch {}
488
+ });
489
+ });
490
+
491
+ ensureScript('https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js', () => { try { renderPreview(getMarkdown()); } catch {} });
492
+ setupEditor();
493
+ setupSplit();
494
+ bindActions();
495
+
496
+ const aboutBtn = document.getElementById('btn-about');
497
+ if (aboutBtn) {
498
+ const aboutEl = document.getElementById('aboutModal');
499
+ const aboutModal = new bootstrap.Modal(aboutEl);
500
+ aboutBtn.addEventListener('click', () => aboutModal.show());
501
+
502
+ aboutEl.addEventListener('hidden.bs.modal', () => {
503
+ document.body.classList.remove('modal-open');
504
+ document.body.style.removeProperty('padding-right');
505
+ document.querySelectorAll('.modal-backdrop').forEach(b => b.remove());
506
+
507
+ document.documentElement.style.overflow = '';
508
+ document.body.style.overflow = '';
509
+ });
510
+ }
511
+ });
static/style.css ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* General */
2
+ :root {
3
+ --bg: #0f172a; /* slate-900 */
4
+ --panel: #0b1220; /* deep card */
5
+ --panel-contrast: #0b1220f5;
6
+ --text: #e2e8f0; /* slate-200 */
7
+ --muted: #94a3b8; /* slate-400 */
8
+ --border: #1f2937; /* slate-800 */
9
+ --accent: #3b82f6; /* blue-500 */
10
+ --code-bg: #0b1220;
11
+ --code-border: #1f2937;
12
+ --blockquote-bg: rgba(255,255,255,0.03);
13
+ --toolbar-icon: #e5e7eb; /* brighter by default in dark */
14
+ --toolbar-icon-active: #ffffff; /* bright for active/hover */
15
+ --toolbar-bg: var(--panel);
16
+ --toolbar-hover: rgba(255,255,255,0.06);
17
+ --toolbar-shadow: 0 2px 8px rgba(0,0,0,.25);
18
+ }
19
+ .light {
20
+ --bg: #f6f8fa;
21
+ --panel: #ffffff;
22
+ --panel-contrast: #ffffffcc;
23
+ --text: #24292f;
24
+ --muted: #6b7280;
25
+ --border: #d0d7de;
26
+ --accent: #2563eb;
27
+ --code-bg: #f6f8fa;
28
+ --code-border: #d0d7de;
29
+ --blockquote-bg: #f8fafc;
30
+ --toolbar-icon: #1f2937; /* darker for light bg */
31
+ --toolbar-icon-active: #111827;
32
+ --toolbar-bg: #ffffff;
33
+ --toolbar-hover: rgba(0,0,0,0.06);
34
+ --toolbar-shadow: 0 2px 10px rgba(0,0,0,.08);
35
+ }
36
+ html, body { height: 100%; }
37
+ body {
38
+ background: radial-gradient(1200px 800px at 10% -10%, #1f2937 0%, var(--bg) 40%),
39
+ radial-gradient(800px 600px at 110% 10%, #0b1220 0%, var(--bg) 50%);
40
+ color: var(--text);
41
+ font-family: 'Inter', 'Roboto', 'Segoe UI', Arial, sans-serif;
42
+ }
43
+ .light body { background: var(--bg); }
44
+
45
+ /* Editor and preview */
46
+ #preview { background: transparent; }
47
+
48
+ .EasyMDEContainer { height: 100%; display: flex; flex-direction: column; }
49
+ .EasyMDEContainer .CodeMirror { flex: 1 1 auto; height: 100% !important; font-size: 0.98rem; background: var(--panel); color: var(--text); }
50
+ /* Enhanced toolbar layout for consistent alignment */
51
+ .editor-toolbar {
52
+ background: var(--toolbar-bg);
53
+ border-color: var(--border) !important;
54
+ border-radius: 12px 12px 0 0;
55
+ box-shadow: var(--toolbar-shadow);
56
+ padding: 6px 8px;
57
+ line-height: 1; /* prevent baseline drift */
58
+ }
59
+ .editor-toolbar a {
60
+ color: var(--toolbar-icon) !important;
61
+ display: inline-flex !important;
62
+ align-items: center !important;
63
+ justify-content: center !important;
64
+ height: 34px !important;
65
+ min-width: 34px !important;
66
+ padding: 0 8px !important;
67
+ margin: 2px 3px !important;
68
+ border-radius: 8px !important;
69
+ transition: background .15s ease, color .15s ease, transform .05s ease;
70
+ font-size: 1.05rem !important;
71
+ }
72
+ .editor-toolbar a:hover, .editor-toolbar a.active {
73
+ color: var(--toolbar-icon-active) !important;
74
+ background: var(--toolbar-hover) !important;
75
+ }
76
+ /* Ensure all icon glyphs inherit the color (FA4 + FA6 + SVGs) */
77
+ .editor-toolbar .fa, .editor-toolbar .fa:before,
78
+ .editor-toolbar svg { color: inherit !important; fill: currentColor !important; }
79
+ .editor-toolbar i.separator { border-color: var(--border) !important; opacity: .7; margin: 0 6px !important; }
80
+ .CodeMirror-cursor { border-left: 2px solid var(--accent) !important; }
81
+ .CodeMirror-gutters { background: var(--panel); border-right: 1px solid var(--border); }
82
+ .cm-s-default .CodeMirror-linenumber { color: var(--muted); }
83
+
84
+ /* Make the separators visible in dark */
85
+ .editor-toolbar i.separator { border-color: var(--border) !important; opacity: .7; }
86
+ /* Ensure FA4 icons inherit color properly */
87
+ .editor-toolbar .fa, .editor-toolbar .fa:before { color: inherit; }
88
+ /* make icons aligned */
89
+ .editor-toolbar .fa { width: 18px; text-align: center; }
90
+
91
+ .navbar .nav-link, .navbar-brand { color: inherit; }
92
+ .navbar .dropdown-item i { width: 1.25rem; }
93
+ /* Ensure brand has correct color in dark mode and on hover */
94
+ .dark .navbar .navbar-brand { color: #e5e7eb !important; }
95
+ .dark .navbar .navbar-brand:hover { color: #ffffff !important; background: transparent !important; }
96
+
97
+ /* Markdown content */
98
+ .markdown-body { max-width: 100%; color: var(--text); line-height: 1.7; }
99
+ .markdown-body h1, .markdown-body h2, .markdown-body h3,
100
+ .markdown-body h4, .markdown-body h5, .markdown-body h6 { color: var(--text); margin-top: 1.25rem; font-weight: 700; }
101
+ .markdown-body p, .markdown-body li { color: var(--text); }
102
+ .markdown-body a { color: var(--accent); text-decoration: none; }
103
+ .markdown-body a:hover { text-decoration: underline; }
104
+ .markdown-body hr { border: 0; border-top: 1px solid var(--border); margin: 1.25rem 0; }
105
+ .markdown-body blockquote { border-left: 4px solid var(--border); padding: .5rem 1rem; color: var(--muted); background: var(--blockquote-bg); }
106
+ .markdown-body pre, .markdown-body code { background: var(--code-bg); border: 1px solid var(--code-border); color: var(--text); }
107
+ .markdown-body pre { padding: .75rem; border-radius: 8px; overflow: auto; }
108
+ .markdown-body table { border-collapse: collapse; width: 100%; background: var(--panel); }
109
+ /* Stronger specificity to override Bootstrap's .table */
110
+ .markdown-body table th, .markdown-body table td { border: 1px solid var(--border); padding: .6rem .8rem; color: var(--text) !important; opacity: 1 !important; }
111
+ .markdown-body table thead th { color: var(--text) !important; }
112
+ .markdown-body table tbody td { color: var(--text) !important; }
113
+ .markdown-body table tr, .markdown-body table td, .markdown-body table th { filter: none !important; }
114
+ .markdown-body table th { background: #1b2433; color: #e2e8f0 !important; font-weight: 600; }
115
+ /* Dark mode body cell backgrounds for contrast */
116
+ .markdown-body table td { background: rgba(255,255,255,0.065); }
117
+ .markdown-body tr:nth-child(even) td { background: rgba(255,255,255,0.06); }
118
+ .markdown-body tr:hover td { background: rgba(59,130,246,0.12); }
119
+ .light .markdown-body th { background: #f0f3f6; color: #24292f; }
120
+ .light .markdown-body tr:nth-child(even) td { background: #fafbfc; }
121
+ #preview img { max-width: 100%; height: auto; }
122
+
123
+ /* Hard overrides for dark-mode table inside preview to defeat any framework table styles */
124
+ .dark #preview .markdown-body table { background: var(--panel) !important; border-color: var(--border) !important; }
125
+ .dark #preview .markdown-body table td,
126
+ .dark #preview .markdown-body table th {
127
+ color: var(--text) !important;
128
+ border-color: var(--border) !important;
129
+ opacity: 1 !important;
130
+ filter: none !important;
131
+ }
132
+ .dark #preview .markdown-body table td { background-color: rgba(255,255,255,0.07) !important; }
133
+ .dark #preview .markdown-body tr:nth-child(even) td { background-color: rgba(255,255,255,0.1) !important; }
134
+ .dark #preview .markdown-body tr:hover td { background-color: rgba(59,130,246,0.18) !important; }
135
+
136
+ /* Force bright text for all table content in dark mode */
137
+ .dark .markdown-body table,
138
+ .dark .markdown-body table * {
139
+ color: var(--text) !important;
140
+ opacity: 1 !important;
141
+ }
142
+ /* Extra specificity for body cells and their content */
143
+ .dark .markdown-body table td,
144
+ .dark .markdown-body table td * { color: var(--text) !important; }
145
+ /* Max specificity hammer for stubborn overrides */
146
+ .dark #preview .markdown-body table tbody td,
147
+ .dark #preview .markdown-body table tbody td * {
148
+ color: #e5e7eb !important;
149
+ opacity: 1 !important;
150
+ }
151
+
152
+ /* Utility */
153
+ button.nav-link { cursor: pointer; }
154
+
155
+ /* Modern shell */
156
+ .app-header {
157
+ backdrop-filter: blur(12px);
158
+ background: linear-gradient(180deg, var(--panel-contrast), transparent);
159
+ border-bottom: 1px solid var(--border);
160
+ }
161
+ .modal-open .app-header { backdrop-filter: none !important; background: var(--panel) !important; }
162
+ .app-shell { min-height: calc(100vh - 64px); }
163
+ .pane { display: flex; flex-direction: column; min-width: 0; }
164
+ .card-pane {
165
+ background: linear-gradient(180deg, var(--panel), var(--panel));
166
+ border: 1px solid var(--border);
167
+ border-radius: 14px;
168
+ box-shadow: 0 10px 30px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.02);
169
+ }
170
+ .light .card-pane { box-shadow: 0 10px 24px rgba(0,0,0,.08), inset 0 1px 0 rgba(255,255,255,.6); }
171
+ .pane-header {
172
+ padding: .75rem 1rem;
173
+ border-bottom: 1px solid var(--border);
174
+ color: var(--text);
175
+ }
176
+ .pane-header .text-muted { color: var(--muted) !important; }
177
+
178
+ /* List group harmonization in modal */
179
+ .list-group-item { background: var(--panel); color: var(--text); border-color: var(--border); }
180
+ .modal-content.bg-body { background: var(--panel); color: var(--text); border-color: var(--border); }
181
+
182
+ /* Theme toggle */
183
+ .form-switch .form-check-input { cursor: pointer; }
184
+
185
+ /* Slightly lighter CodeMirror caret & line-height for readability */
186
+ .EasyMDEContainer .CodeMirror { line-height: 1.55; }
187
+
188
+ /* Toolbar can wrap into multiple rows (left-aligned) */
189
+ .editor-toolbar {
190
+ display: flex !important;
191
+ flex-wrap: wrap !important; /* allow wrapping */
192
+ align-items: center !important;
193
+ white-space: normal !important;
194
+ overflow: visible !important; /* no horizontal scrollbar */
195
+ text-align: left !important;
196
+ gap: 2px 4px; /* spacing between rows/items */
197
+ }
198
+ .editor-toolbar a, .editor-toolbar i.separator {
199
+ display: inline-flex !important;
200
+ flex: 0 0 auto !important;
201
+ vertical-align: middle !important;
202
+ }
203
+ /* Keep table icon aligned */
204
+ .editor-toolbar .fa-table { line-height: 1 !important; font-size: 1.05rem !important; }
205
+
206
+ /* Fix Bootstrap conflict: EasyMDE 'table' toolbar button should not take full width */
207
+ .editor-toolbar a.table,
208
+ .editor-toolbar button.table {
209
+ display: inline-flex !important;
210
+ width: auto !important;
211
+ min-width: 0 !important;
212
+ height: 34px !important;
213
+ align-items: center !important;
214
+ justify-content: center !important;
215
+ margin: 2px 3px !important;
216
+ padding: 0 8px !important;
217
+ }
218
+
219
+ /* Ensure navbar dropdown overlays editor panes */
220
+ .navbar { position: relative; z-index: 1500; }
221
+ .dropdown-menu { z-index: 2000; }
222
+
223
+ /* Force FA6 icons to render in dropdown */
224
+ .dropdown-menu .fa, .dropdown-menu .fa-solid, .dropdown-menu i.fa-solid {
225
+ font-family: "Font Awesome 6 Free" !important;
226
+ font-weight: 900 !important;
227
+ display: inline-block;
228
+ width: 1.25rem;
229
+ text-align: center;
230
+ }
231
+ /* Also ensure icon color follows text */
232
+ .dropdown-menu i { color: inherit; }
233
+
234
+ /* Dropdown theming */
235
+ .dropdown-menu { border-radius: 10px; border: 1px solid var(--border); box-shadow: 0 8px 24px rgba(0,0,0,.15); }
236
+ .dropdown-menu .dropdown-item { padding: .6rem .9rem; display: flex; align-items: center; gap: .6rem; }
237
+ .dropdown-menu .dropdown-item i { width: 1.1rem; text-align: center; }
238
+
239
+ .dark .dropdown-menu { background: #0f172a; color: #e5e7eb; border-color: #1f2937; box-shadow: 0 12px 28px rgba(0,0,0,.45); }
240
+ .dark .dropdown-menu .dropdown-item { color: #e5e7eb; }
241
+ .dark .dropdown-menu .dropdown-item:hover { background: rgba(255,255,255,0.08); color: #fff; }
242
+ /* caret contrast */
243
+ .dropdown-menu::before { border-bottom-color: var(--border); }
244
+
245
+ /* Dark mode overrides to make toolbar icons white */
246
+ .dark .editor-toolbar a { color: #ffffff !important; }
247
+ .dark .editor-toolbar a:hover, .dark .editor-toolbar a.active { color: #ffffff !important; background: rgba(255,255,255,0.12) !important; }
248
+ .dark .editor-toolbar .fa, .dark .editor-toolbar .fa:before, .dark .editor-toolbar svg { color: #ffffff !important; fill: #ffffff !important; }
249
+
250
+ /* Dark mode toolbar hover/focus */
251
+ .dark .editor-toolbar a:hover,
252
+ .dark .editor-toolbar a:focus,
253
+ .dark .editor-toolbar a.active {
254
+ background: rgba(255,255,255,0.18) !important;
255
+ outline: none !important;
256
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,0.25) !important;
257
+ }
258
+ .dark .editor-toolbar a:focus-visible {
259
+ box-shadow: inset 0 0 0 2px #60a5fa !important; /* blue ring for keyboard focus */
260
+ }
261
+
262
+ /* Dark mode toolbar buttons - unified look */
263
+ .dark .editor-toolbar a,
264
+ .dark .editor-toolbar button {
265
+ color: #ffffff !important;
266
+ border-radius: 8px !important;
267
+ transition: background .15s ease, box-shadow .15s ease, color .15s ease;
268
+ }
269
+ /* Softer hover/active (no solid white block) */
270
+ .dark .editor-toolbar a:hover,
271
+ .dark .editor-toolbar button:hover,
272
+ .dark .editor-toolbar a.active,
273
+ .dark .editor-toolbar button.active {
274
+ background: rgba(255,255,255,0.12) !important;
275
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,0.18) !important;
276
+ }
277
+ /* Remove any default white background from library styles */
278
+ .dark .editor-toolbar a:hover *,
279
+ .dark .editor-toolbar button:hover * { background: transparent !important; }
280
+
281
+ /* Navbar icons ensure FA6 solid */
282
+ .navbar .fa-solid { font-family: "Font Awesome 6 Free" !important; font-weight: 900 !important; }
283
+
284
+ /* Document title input */
285
+ .doc-title-input { width: 220px; background: transparent; color: var(--text); border-color: var(--border); }
286
+ .doc-title-input::placeholder { color: var(--muted); }
287
+ .dark .doc-title-input { background: rgba(255,255,255,0.06); color: #e5e7eb; border-color: #374151; }
288
+ .dark .doc-title-input:focus { background: rgba(255,255,255,0.09); color: #fff; }
289
+
290
+ /* Navbar button alignment */
291
+ .navbar .nav-link,
292
+ .navbar .dropdown-toggle {
293
+ display: inline-flex;
294
+ align-items: center;
295
+ gap: 6px;
296
+ padding: 6px 10px;
297
+ border-radius: 8px;
298
+ }
299
+ .navbar .nav-link i,
300
+ .navbar .dropdown-toggle i { color: inherit; }
301
+ /* Light hover */
302
+ .navbar .nav-link:hover,
303
+ .navbar .dropdown-toggle:hover { background: rgba(0,0,0,0.06); }
304
+ /* Dark hover */
305
+ .dark .navbar .nav-link,
306
+ .dark .navbar .dropdown-toggle { color: #e5e7eb; }
307
+ .dark .navbar .nav-link:hover,
308
+ .dark .navbar .dropdown-toggle:hover { background: rgba(255,255,255,0.12); color: #ffffff; }
309
+ /* Ensure export caret/icon position */
310
+ .navbar .dropdown-toggle::after { margin-left: 6px; }
311
+
312
+ .doc-title-pill {
313
+ display: inline-block;
314
+ max-width: 360px;
315
+ padding: 4px 10px;
316
+ border-radius: 10px;
317
+ background: rgba(0,0,0,0.04);
318
+ border: 1px solid var(--border);
319
+ color: var(--text);
320
+ cursor: text;
321
+ }
322
+ .doc-title-pill:focus { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
323
+ .dark .doc-title-pill { background: rgba(255,255,255,0.06); border-color: #374151; color: #e5e7eb; }
324
+
325
+ /* Theme button */
326
+ #btn-theme { display: inline-flex; align-items: center; gap: 6px; }
327
+ .dark #btn-theme { color: #e5e7eb; }
328
+ .dark #btn-theme:hover { background: rgba(255,255,255,0.12); border-radius: 8px; }
329
+
330
+ /* Prevent dropdown text cutoff and hide arrow caret */
331
+ .dropdown-menu { min-width: 260px; }
332
+ .dropdown-menu .dropdown-item { white-space: nowrap; }
333
+ .dropdown-menu::before, .dropdown-menu::after { display: none !important; }
334
+
335
+ /* Read mode helper: smooth layout change */
336
+ #leftPane, #rightPane { transition: all .2s ease; }
337
+
338
+ /* Read mode: preview full page, hide editor */
339
+ .read-mode .app-header { position: sticky; top: 0; z-index: 1500; }
340
+ .read-mode #leftPane { display: none !important; }
341
+ .read-mode #rightPane { width: 100% !important; flex: 1 1 auto; }
342
+ .read-mode #rightPane[class*="col-"] { float: none; max-width: 100%; }
343
+ /* Expand row height in read mode */
344
+ .read-mode .app-shell .row { height: calc(100vh - 72px) !important; }
345
+
346
+ /* Footer behavior: normal flow in default; fixed at bottom in read mode */
347
+ .read-mode .footer-credit-wrap {
348
+ position: fixed;
349
+ left: 0;
350
+ right: 0;
351
+ bottom: 20px;
352
+ z-index: 1500;
353
+ }
354
+ .read-mode .footer-credit-wrap .footer-credit {
355
+ background: rgba(0,0,0,0.8);
356
+ color: #ffffff;
357
+ border: 1px solid rgba(255,255,255,0.2);
358
+ }
359
+ .read-mode .footer-credit-wrap .footer-credit { pointer-events: auto; }
360
+
361
+ /* Samples modal theming */
362
+ #samplesModal .modal-content { border: 1px solid var(--border); }
363
+ #samplesModal .list-group-item { background: var(--panel); color: var(--text); border-color: var(--border); }
364
+ #samplesModal .list-group-item .fw-semibold { color: var(--text); }
365
+ #samplesModal .text-muted { color: var(--muted) !important; }
366
+ #samplesModal .btn.btn-outline-primary { color: var(--text); border-color: var(--border); }
367
+ #samplesModal .btn.btn-outline-primary:hover { background: var(--toolbar-hover); color: var(--text); }
368
+ #samplesModal .btn.btn-outline-secondary { color: var(--text); border-color: var(--border); }
369
+ #samplesModal .btn.btn-outline-secondary:hover { background: var(--toolbar-hover); color: var(--text); }
370
+ /* Light mode hover contrast */
371
+ .light #samplesModal .btn:hover { background: rgba(0,0,0,0.06); }
372
+
373
+ /* Global modal dark-mode fixes */
374
+ .dark .modal-content { background: var(--panel); color: var(--text); border-color: var(--border); }
375
+ .dark .modal-header, .dark .modal-footer { border-color: var(--border); }
376
+ .dark .modal-title { color: var(--text); }
377
+ .dark .btn-close { filter: invert(1) opacity(0.8); }
378
+ .dark .modal .btn-outline-primary, .dark .modal .btn-outline-secondary { color: var(--text); border-color: var(--border); }
379
+ .dark .modal .btn-outline-primary:hover, .dark .modal .btn-outline-secondary:hover { background: var(--toolbar-hover); color: #fff; }
380
+ /* About modal cards and links */
381
+ .dark .modal .card.bg-body-secondary { background: rgba(255,255,255,0.06); border: 1px solid var(--border); color: var(--text); }
382
+ .dark .modal .card.bg-body-secondary * { color: var(--text) !important; }
383
+ .dark .modal a { color: var(--accent); }
384
+ .dark .modal a:hover { text-decoration: underline; }
385
+ /* About modal: ensure strong contrast in dark mode */
386
+ .dark #aboutModal .modal-content { background: var(--panel); color: var(--text); border-color: var(--border); }
387
+ .dark #aboutModal .modal-header, .dark #aboutModal .modal-footer { border-color: var(--border); }
388
+ .dark #aboutModal .modal-title { color: var(--text); }
389
+ .dark #aboutModal .modal-body p,
390
+ .dark #aboutModal .modal-body li,
391
+ .dark #aboutModal .modal-body .fw-semibold { color: var(--text) !important; }
392
+ .dark #aboutModal .card { background: rgba(255,255,255,0.07) !important; border: 1px solid var(--border) !important; }
393
+ .dark #aboutModal .card * { color: var(--text) !important; }
394
+ .dark #aboutModal a { color: var(--accent) !important; }
395
+ .dark #aboutModal a:hover { text-decoration: underline; }
396
+
397
+ /* Footer credit box */
398
+ .footer-credit {
399
+ padding: 10px 18px;
400
+ border-radius: 14px;
401
+ background: linear-gradient(90deg, rgba(255,255,255,0.9), rgba(59,130,246,0.15));
402
+ color: #1f2937;
403
+ font-weight: 600;
404
+ box-shadow: 0 6px 18px rgba(0,0,0,.12);
405
+ border: 1px solid rgba(0,0,0,0.06);
406
+ }
407
+ .dark .footer-credit {
408
+ background: linear-gradient(90deg, rgba(17,24,39,0.9), rgba(59,130,246,0.25));
409
+ color: #e5e7eb;
410
+ border-color: rgba(255,255,255,0.08);
411
+ }
412
+
413
+ .footer-credit-wrap { padding: 16px 0 24px 0; }
414
+ .read-mode .footer-credit-wrap { display: none !important; }
templates/index.html ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% set title = 'Rich Markdown Editor' %}
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{ title }}</title>
8
+ <meta name="author" content="Aradhya Pavan H S" />
9
+ <link rel="author" href="https://github.com/aradhyapavan" />
10
+ <meta name="application-name" content="Rich Markdown Editor" />
11
+ <meta name="generator" content="Designgen developed by Aradhya Pavan" />
12
+ <meta name="description" content="Free Markdown editor and online Markdown previewer. Convert Markdown to HTML and export to PDF. Developed by Aradhya Pavan." />
13
+ <meta name="keywords" content="Free Markdown editor, Online Markdown Previewer, Markdown to HTML, Markdown to PDF, Markdown editor online, Markdown converter, Markdown viewer, HTML export, PDF export, EasyMDE, KaTeX, Mermaid, GitHub-style Markdown, Designgen, Aradhya Pavan H S, Aradhya Pavan" />
14
+ <meta name="robots" content="index, follow" />
15
+ <link rel="canonical" href="https://github.com/aradhyapavan" />
16
+ <meta property="og:title" content="{{ title }}" />
17
+ <meta property="og:description" content="Free Markdown editor and online Markdown previewer. Convert Markdown to HTML and export to PDF. Developed by Aradhya Pavan." />
18
+ <meta property="og:type" content="website" />
19
+ <meta property="og:url" content="https://github.com/aradhyapavan" />
20
+ <meta name="twitter:card" content="summary" />
21
+ <meta name="twitter:title" content="{{ title }}" />
22
+ <meta name="twitter:description" content="Free Markdown editor and online Markdown previewer. Convert Markdown to HTML and export to PDF. Developed by Aradhya Pavan." />
23
+ <meta name="theme-color" content="#0f172a" />
24
+ <link rel="icon" href="{{ url_for('static', filename='favicon/favicon.ico') }}" type="image/x-icon" />
25
+ <link rel="shortcut icon" href="{{ url_for('static', filename='favicon/favicon.ico') }}" type="image/x-icon" />
26
+ <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon/favicon.svg') }}" />
27
+ <link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon/android-chrome-512x512.png') }}" />
28
+ <link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon/android-chrome-512x512.png') }}" />
29
+ <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='favicon/android-chrome-512x512.png') }}" />
30
+ <link rel="manifest" href="{{ url_for('static', filename='favicon/site.webmanifest') }}" />
31
+
32
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" />
33
+
34
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
35
+
36
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet" />
37
+
38
+ <link href="https://unpkg.com/@primer/css@21.0.7/dist/primer.css" rel="stylesheet" />
39
+
40
+ <link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css" />
41
+
42
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
43
+ </head>
44
+ <body>
45
+
46
+ <nav class="navbar navbar-expand-lg navbar-light app-header px-3">
47
+ <span class="navbar-brand fw-bold d-flex align-items-center"><i class="fa-solid fa-file-lines me-2"></i>Markdown Editor</span>
48
+ <div class="collapse navbar-collapse show">
49
+ <div class="ms-3 me-3 d-flex align-items-center gap-2">
50
+ <span class="text-muted small">File name</span>
51
+ <span id="doc-title-pill" class="doc-title-pill" contenteditable="true" spellcheck="false" title="Click to rename" aria-label="File name">Welcome file</span>
52
+ </div>
53
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
54
+ <li class="nav-item"><button class="btn btn-link nav-link" id="btn-import-md"><i class="fa-solid fa-upload"></i> Import Markdown</button></li>
55
+ <li class="nav-item"><button class="btn btn-link nav-link" id="btn-import-html"><i class="fa-solid fa-file-import"></i> Import HTML</button></li>
56
+ <li class="nav-item"><button class="btn btn-link nav-link" id="btn-samples"><i class="fa-solid fa-wand-magic-sparkles"></i> Samples</button></li>
57
+ <li class="nav-item dropdown">
58
+ <a class="nav-link dropdown-toggle" href="#" id="exportDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="fa-solid fa-download"></i> Export</a>
59
+ <ul class="dropdown-menu" aria-labelledby="exportDropdown">
60
+ <li><button class="dropdown-item" id="btn-export-md"><i class="fa-solid fa-file-lines"></i> Markdown</button></li>
61
+ <li><button class="dropdown-item" id="btn-export-html"><i class="fa-solid fa-code"></i> HTML (raw)</button></li>
62
+ <li><button class="dropdown-item" id="btn-export-html-styled"><i class="fa-solid fa-paintbrush"></i> HTML (with styles)</button></li>
63
+ <li><hr class="dropdown-divider"></li>
64
+ <li><button class="dropdown-item" id="btn-export-pdf"><i class="fa-solid fa-file-pdf"></i> PDF</button></li>
65
+ </ul>
66
+ </li>
67
+ </ul>
68
+ <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
69
+ <li class="nav-item"><button class="btn btn-link nav-link" id="btn-readmode"><i class="fa-solid fa-book-open"></i> Read mode</button></li>
70
+ <li class="nav-item"><button class="btn btn-link nav-link" id="btn-theme"><i class="fa-solid fa-moon"></i> Theme</button></li>
71
+ <li class="nav-item"><button class="btn btn-link nav-link" id="btn-about"><i class="fa-solid fa-user"></i> About</button></li>
72
+ </ul>
73
+ </div>
74
+ </nav>
75
+
76
+ <div class="container-fluid p-3 app-shell">
77
+ <div class="row g-3">
78
+ <div id="leftPane" class="col-md-6 d-flex flex-column pane card-pane">
79
+ <div class="pane-header d-flex align-items-center justify-content-between"><span class="fw-semibold"><i class="fa-solid fa-pen"></i> Editor</span><span class="text-muted small" id="status-line">Ready</span></div>
80
+ <textarea id="editor"></textarea>
81
+ </div>
82
+ <div id="rightPane" class="col-md-6 d-flex flex-column pane card-pane">
83
+ <div class="pane-header d-flex align-items-center justify-content-between">
84
+ <span class="fw-semibold"><i class="fa-solid fa-eye"></i> Preview</span>
85
+ </div>
86
+ <div id="preview" class="p-4 flex-grow-1 overflow-auto"></div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+
91
+
92
+ <div class="modal fade" id="aboutModal" tabindex="-1" aria-labelledby="aboutLabel" aria-hidden="true">
93
+ <div class="modal-dialog modal-lg modal-dialog-centered">
94
+ <div class="modal-content">
95
+ <div class="modal-header">
96
+ <h5 class="modal-title" id="aboutLabel"><i class="fa-solid fa-user-astronaut me-2"></i>About the Developer</h5>
97
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
98
+ </div>
99
+ <div class="modal-body">
100
+ <div class="d-flex flex-column gap-2">
101
+ <p class="mb-0">Hi, I'm <strong>Aradhya Pavan H S</strong> — a passionate techie who loves building useful tools and contributing to open source. I got fed up with online Markdown tools and their limitations, so I built this clean, fast editor with a great preview and rich exports (Markdown, HTML, styled HTML, PDF). I built it for myself—feel free to clone it and use it.</p>
102
+ <div class="row g-3 mt-1">
103
+ <div class="col-md-6">
104
+ <div class="card bg-body-secondary border-0">
105
+ <div class="card-body py-3">
106
+ <div class="fw-semibold mb-1"><i class="fa-solid fa-bolt me-2"></i>What I do</div>
107
+ <ul class="mb-0 small">
108
+ <li>UI engineering and component libraries</li>
109
+ <li>APIs, automation, and tooling</li>
110
+ <li>Docs, DX, and product polish</li>
111
+ <li>Contributing to open source and community tooling</li>
112
+ </ul>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ <div class="col-md-6">
117
+ <div class="card bg-body-secondary border-0">
118
+ <div class="card-body py-3">
119
+ <div class="fw-semibold mb-1"><i class="fa-solid fa-link me-2"></i>Links</div>
120
+ <ul class="mb-0 small">
121
+ <li><a href="https://aradhyapavan.github.io/" target="_blank" rel="noopener">Personal website</a></li>
122
+ <li><a href="https://github.com/aradhyapavan" target="_blank" rel="noopener">GitHub</a></li>
123
+ </ul>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ <div class="row g-3 mt-1">
129
+ <div class="col-12">
130
+ <div class="card bg-body-secondary border-0">
131
+ <div class="card-body py-3">
132
+ <div class="fw-semibold mb-1"><i class="fa-solid fa-star me-2"></i>Features</div>
133
+ <ul class="mb-0 small">
134
+ <li>Rich Markdown editor toolbar (headings, lists, tables, code, images, links)</li>
135
+ <li>Live preview with GitHub-style rendering</li>
136
+ <li>KaTeX math typesetting and Mermaid diagrams (sequence, flow, gantt)</li>
137
+ <li>Exports: Markdown (.md), HTML (raw), HTML (with styles), and PDF</li>
138
+ <li>Read mode for distraction-free full-page preview</li>
139
+ <li>Dark/Light themes with persistent preference</li>
140
+ <li>Editable file name in navbar; used in exports</li>
141
+ <li>Sample templates (README, Math Notes, API Docs, Meeting Notes)</li>
142
+ <li>Import Markdown or HTML files</li>
143
+ </ul>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ <div class="modal-footer">
151
+ <button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+
158
+ <div class="modal fade" id="pdfOptionsModal" tabindex="-1" aria-labelledby="pdfOptionsLabel" aria-hidden="true">
159
+ <div class="modal-dialog modal-dialog-centered">
160
+ <div class="modal-content">
161
+ <div class="modal-header">
162
+ <h5 class="modal-title" id="pdfOptionsLabel"><i class="fa-solid fa-file-pdf me-2"></i>PDF Export Options</h5>
163
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
164
+ </div>
165
+ <div class="modal-body">
166
+ <div class="row g-3">
167
+ <div class="col-md-6">
168
+ <label class="form-label fw-semibold">Page Orientation</label>
169
+ <div class="form-check">
170
+ <input class="form-check-input" type="radio" name="orientation" id="portrait" value="portrait" checked>
171
+ <label class="form-check-label" for="portrait">
172
+ <i class="fa-solid fa-arrow-down me-2"></i>Portrait
173
+ </label>
174
+ </div>
175
+ <div class="form-check">
176
+ <input class="form-check-input" type="radio" name="orientation" id="landscape" value="landscape">
177
+ <label class="form-check-label" for="landscape">
178
+ <i class="fa-solid fa-arrow-right me-2"></i>Landscape
179
+ </label>
180
+ </div>
181
+ </div>
182
+ <div class="col-md-6">
183
+ <label class="form-label fw-semibold">Paper Size</label>
184
+ <select class="form-select" id="paperSize">
185
+ <option value="a4">A4 (210 × 297 mm)</option>
186
+ <option value="letter">Letter (8.5 × 11 in)</option>
187
+ <option value="legal">Legal (8.5 × 14 in)</option>
188
+ <option value="a3">A3 (297 × 420 mm)</option>
189
+ <option value="a5">A5 (148 × 210 mm)</option>
190
+ </select>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ <div class="modal-footer">
195
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
196
+ <button type="button" class="btn btn-primary" id="btn-generate-pdf">Generate PDF</button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+
202
+
203
+ <input type="file" id="file-md" accept=".md,.markdown,.txt" hidden>
204
+ <input type="file" id="file-html" accept=".html,.htm" hidden>
205
+
206
+
207
+ <div class="modal fade" id="samplesModal" tabindex="-1" aria-labelledby="samplesLabel" aria-hidden="true">
208
+ <div class="modal-dialog modal-xl modal-dialog-centered">
209
+ <div class="modal-content bg-body">
210
+ <div class="modal-header">
211
+ <h5 class="modal-title" id="samplesLabel"><i class="fa-solid fa-wand-magic-sparkles me-2"></i>Insert Markdown Samples</h5>
212
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
213
+ </div>
214
+ <div class="modal-body">
215
+ <div class="list-group">
216
+ <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
217
+ <div>
218
+ <div class="fw-semibold">Full Cheat Sheet</div>
219
+ <div class="text-muted small">Headings, emphasis, lists, tables, code, tasks, links, images, quotes, rules.</div>
220
+ </div>
221
+ <div>
222
+ <button class="btn btn-outline-primary me-2" data-sample="cheatsheet">Insert (replace)</button>
223
+ <button class="btn btn-outline-secondary" data-sample-append="cheatsheet">Append</button>
224
+ </div>
225
+ </div>
226
+ <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
227
+ <div>
228
+ <div class="fw-semibold">Email Template</div>
229
+ <div class="text-muted small">Professional email with bullet lists and signature.</div>
230
+ </div>
231
+ <div>
232
+ <button class="btn btn-outline-primary me-2" data-sample="email">Insert (replace)</button>
233
+ <button class="btn btn-outline-secondary" data-sample-append="email">Append</button>
234
+ </div>
235
+ </div>
236
+ <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
237
+ <div>
238
+ <div class="fw-semibold">README Starter</div>
239
+ <div class="text-muted small">Project badges, install, usage, features, license.</div>
240
+ </div>
241
+ <div>
242
+ <button class="btn btn-outline-primary me-2" data-sample="readme">Insert (replace)</button>
243
+ <button class="btn btn-outline-secondary" data-sample-append="readme">Append</button>
244
+ </div>
245
+ </div>
246
+ <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
247
+ <div>
248
+ <div class="fw-semibold">Pro GitHub README</div>
249
+ <div class="text-muted small">Badges, stack table, structure tree, CLI, Mermaid diagram.</div>
250
+ </div>
251
+ <div>
252
+ <button class="btn btn-outline-primary me-2" data-sample="github_readme_pro">Insert (replace)</button>
253
+ <button class="btn btn-outline-secondary" data-sample-append="github_readme_pro">Append</button>
254
+ </div>
255
+ </div>
256
+ <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
257
+ <div>
258
+ <div class="fw-semibold">Math Notes</div>
259
+ <div class="text-muted small">KaTeX inline/block formulas with references.</div>
260
+ </div>
261
+ <div>
262
+ <button class="btn btn-outline-primary me-2" data-sample="math_notes">Insert (replace)</button>
263
+ <button class="btn btn-outline-secondary" data-sample-append="math_notes">Append</button>
264
+ </div>
265
+ </div>
266
+ <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
267
+ <div>
268
+ <div class="fw-semibold">API Docs</div>
269
+ <div class="text-muted small">HTTP examples, JSON responses, and error table.</div>
270
+ </div>
271
+ <div>
272
+ <button class="btn btn-outline-primary me-2" data-sample="api_docs">Insert (replace)</button>
273
+ <button class="btn btn-outline-secondary" data-sample-append="api_docs">Append</button>
274
+ </div>
275
+ </div>
276
+ <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap">
277
+ <div>
278
+ <div class="fw-semibold">Meeting Notes + Gantt</div>
279
+ <div class="text-muted small">Agenda, actions, and Mermaid timeline.</div>
280
+ </div>
281
+ <div>
282
+ <button class="btn btn-outline-primary me-2" data-sample="meeting_notes">Insert (replace)</button>
283
+ <button class="btn btn-outline-secondary" data-sample-append="meeting_notes">Append</button>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ <div class="modal-footer">
289
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ </div>
294
+
295
+
296
+ <div class="footer-credit-wrap">
297
+ <div class="d-flex justify-content-center">
298
+ <div class="footer-credit">Designed and Developed by <strong>Aradhya Pavan H S</strong></div>
299
+ </div>
300
+ </div>
301
+
302
+
303
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
304
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
305
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
306
+ <script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
307
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
308
+ <link id="hljs-theme" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
309
+
310
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
311
+
312
+ <script>window.FLASK_EXPORT_HTML_URL = "{{ url_for('export_html') }}"; window.FLASK_EXPORT_MD_URL = "{{ url_for('export_md') }}";</script>
313
+ <script>
314
+ document.addEventListener('DOMContentLoaded', function(){
315
+ const aboutBtn = document.getElementById('btn-about');
316
+ if (aboutBtn) {
317
+ const aboutModal = new bootstrap.Modal(document.getElementById('aboutModal'));
318
+ aboutBtn.addEventListener('click', () => aboutModal.show());
319
+ }
320
+ });
321
+ </script>
322
+ <script src="{{ url_for('static', filename='main.js') }}"></script>
323
+ </body>
324
+ </html>