Spaces:
Running
Running
Readme Editor
Browse files- app.py +63 -0
- requirements.txt +2 -0
- static/favicon/android-chrome-192x192.png +0 -0
- static/favicon/android-chrome-512x512.png +0 -0
- static/favicon/apple-touch-icon.png +0 -0
- static/favicon/favicon-16x16.png +0 -0
- static/favicon/favicon-32x32.png +0 -0
- static/favicon/favicon.ico +0 -0
- static/favicon/site.webmanifest +1 -0
- static/main.js +511 -0
- static/style.css +414 -0
- templates/index.html +324 -0
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\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) \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[](https://github.com/OWNER/REPO/actions) [](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\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>
|