duqing2026 commited on
Commit
2a9883f
·
0 Parent(s):

Initial commit

Browse files
Files changed (5) hide show
  1. Dockerfile +18 -0
  2. README.md +79 -0
  3. app.py +61 -0
  4. requirements.txt +2 -0
  5. templates/index.html +287 -0
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ # Set up a non-root user for Hugging Face Spaces
11
+ RUN useradd -m -u 1000 user
12
+ USER user
13
+ ENV HOME=/home/user \
14
+ PATH=/home/user/.local/bin:$PATH
15
+
16
+ EXPOSE 7860
17
+
18
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Markdown Slides Pro
3
+ emoji: 📽️
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ short_description: Markdown 演示文稿大师
10
+ ---
11
+
12
+ # Markdown 演示文稿大师 (Markdown Slides Pro)
13
+
14
+ 这是一个简单而强大的在线工具,可以帮助你将 Markdown 文本实时转换为精美的幻灯片演示文稿。基于 Reveal.js 构建。
15
+
16
+ ## ✨ 主要功能
17
+
18
+ - **实时预览**:左侧编辑,右侧实时查看幻灯片效果。
19
+ - **多主题支持**:内置 10+ 种精美主题(黑色、白色、夜间、天空等)。
20
+ - **一键导出**:支持将演示文稿导出为独立的 HTML 文件,离线也能播放。
21
+ - **语法丰富**:支持代码高亮、表格、列表、引用等标准 Markdown 语法。
22
+ - **隐私安全**:所有渲染在浏览器端完成,服务器不保存你的内容。
23
+
24
+ ## 🚀 快速开始
25
+
26
+ 1. 在左侧编辑区输入 Markdown 内容。
27
+ 2. 使用 `---` 分隔不同的幻灯片页面。
28
+ 3. 使用 `--` 创建垂直滚动的子页面。
29
+ 4. 点击右上角“导出 HTML”下载演示文稿。
30
+
31
+ ## 🛠️ 技术栈
32
+
33
+ - **后端**: Flask (Python)
34
+ - **前端**: Vue 3 + Tailwind CSS
35
+ - **渲染引擎**: Reveal.js (via CDN)
36
+ - **部署**: Docker
37
+
38
+ ## 📦 本地运行
39
+
40
+ ```bash
41
+ # 克隆项目
42
+ git clone https://github.com/your-username/markdown-slides-pro.git
43
+
44
+ # 进入目录
45
+ cd markdown-slides-pro
46
+
47
+ # 安装依赖
48
+ pip install -r requirements.txt
49
+
50
+ # 运行
51
+ python app.py
52
+ ```
53
+
54
+ 打开浏览器访问 `http://localhost:7860` 即可。
55
+
56
+ ## 📝 语法示例
57
+
58
+ ```markdown
59
+ # 这是一个标题
60
+
61
+ ---
62
+
63
+ ## 第二页
64
+
65
+ - 列表项 1
66
+ - 列表项 2
67
+
68
+ ---
69
+
70
+ ## 代码示例
71
+
72
+ \`\`\`python
73
+ print("Hello World")
74
+ \`\`\`
75
+ ```
76
+
77
+ ## 📄 License
78
+
79
+ MIT
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, Response, send_file
2
+ import os
3
+ import io
4
+
5
+ app = Flask(__name__)
6
+
7
+ @app.route('/')
8
+ def index():
9
+ return render_template('index.html')
10
+
11
+ @app.route('/download', methods=['POST'])
12
+ def download():
13
+ markdown_content = request.form.get('markdown', '')
14
+ theme = request.form.get('theme', 'black')
15
+
16
+ # Generate standalone HTML
17
+ html = f"""<!doctype html>
18
+ <html>
19
+ <head>
20
+ <meta charset="utf-8">
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
22
+
23
+ <title>Presentation</title>
24
+
25
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/reset.min.css">
26
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/reveal.min.css">
27
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/theme/{theme}.min.css">
28
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/monokai.min.css">
29
+ </head>
30
+ <body>
31
+ <div class="reveal">
32
+ <div class="slides">
33
+ <section data-markdown>
34
+ <textarea data-template>
35
+ {markdown_content}
36
+ </textarea>
37
+ </section>
38
+ </div>
39
+ </div>
40
+
41
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/reveal.min.js"></script>
42
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/plugin/notes/notes.min.js"></script>
43
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/plugin/markdown/markdown.min.js"></script>
44
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/plugin/highlight/highlight.min.js"></script>
45
+ <script>
46
+ Reveal.initialize({{
47
+ hash: true,
48
+ plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
49
+ }});
50
+ </script>
51
+ </body>
52
+ </html>"""
53
+
54
+ return Response(
55
+ html,
56
+ mimetype="text/html",
57
+ headers={"Content-disposition": "attachment; filename=presentation.html"}
58
+ )
59
+
60
+ if __name__ == '__main__':
61
+ app.run(host='0.0.0.0', port=7860)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Flask==3.0.0
2
+ gunicorn==21.2.0
templates/index.html ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Markdown 演示文稿大师</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
10
+ <style>
11
+ body, html { height: 100%; overflow: hidden; }
12
+ .editor-container { height: calc(100vh - 64px); }
13
+ textarea { resize: none; outline: none; }
14
+ /* Custom Scrollbar */
15
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
16
+ ::-webkit-scrollbar-track { background: #f1f1f1; }
17
+ ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
18
+ ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
19
+ </style>
20
+ </head>
21
+ <body class="bg-gray-100 text-gray-800">
22
+ <div id="app" class="flex flex-col h-full">
23
+ <!-- Header -->
24
+ <header class="bg-white shadow-sm z-10 px-6 py-3 flex justify-between items-center">
25
+ <div class="flex items-center space-x-3">
26
+ <div class="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center text-white font-bold text-xl">
27
+ <i class="fas fa-presentation-screen"></i>
28
+ </div>
29
+ <h1 class="text-xl font-bold text-gray-800">Markdown 演示文稿大师</h1>
30
+ </div>
31
+
32
+ <div class="flex items-center space-x-4">
33
+ <div class="flex items-center space-x-2">
34
+ <label class="text-sm font-medium text-gray-600">主题:</label>
35
+ <select v-model="currentTheme" @change="updatePreview" class="border rounded px-2 py-1 text-sm bg-gray-50 focus:ring-2 focus:ring-indigo-500">
36
+ <option value="black">黑色 (Black)</option>
37
+ <option value="white">白色 (White)</option>
38
+ <option value="league">联盟 (League)</option>
39
+ <option value="beige">米色 (Beige)</option>
40
+ <option value="sky">天空 (Sky)</option>
41
+ <option value="night">夜间 (Night)</option>
42
+ <option value="serif">衬线 (Serif)</option>
43
+ <option value="simple">简约 (Simple)</option>
44
+ <option value="solarized">曝光 (Solarized)</option>
45
+ <option value="moon">月球 (Moon)</option>
46
+ <option value="dracula">吸血鬼 (Dracula)</option>
47
+ </select>
48
+ </div>
49
+
50
+ <button @click="updatePreview" class="text-indigo-600 hover:text-indigo-800 px-3 py-1 rounded hover:bg-indigo-50 transition">
51
+ <i class="fas fa-sync-alt mr-1"></i> 刷新预览
52
+ </button>
53
+
54
+ <form action="/download" method="POST" target="_blank" class="inline">
55
+ <input type="hidden" name="markdown" :value="markdown">
56
+ <input type="hidden" name="theme" :value="currentTheme">
57
+ <button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center shadow-sm">
58
+ <i class="fas fa-download mr-2"></i> 导出 HTML
59
+ </button>
60
+ </form>
61
+ </div>
62
+ </header>
63
+
64
+ <!-- Main Content -->
65
+ <div class="flex flex-1 overflow-hidden">
66
+ <!-- Editor (Left) -->
67
+ <div class="w-1/2 flex flex-col border-r border-gray-200 bg-white">
68
+ <div class="bg-gray-50 px-4 py-2 border-b border-gray-200 text-xs text-gray-500 flex justify-between">
69
+ <span>Markdown 编辑器</span>
70
+ <span class="text-indigo-500 cursor-pointer hover:underline" @click="showHelp = !showHelp">
71
+ <i class="far fa-question-circle mr-1"></i>语法帮助
72
+ </span>
73
+ </div>
74
+ <textarea
75
+ v-model="markdown"
76
+ @input="debouncedUpdate"
77
+ class="flex-1 p-6 font-mono text-sm leading-relaxed text-gray-700 focus:bg-white bg-gray-50 transition duration-200"
78
+ placeholder="在此输入 Markdown..."
79
+ spellcheck="false"
80
+ ></textarea>
81
+ </div>
82
+
83
+ <!-- Preview (Right) -->
84
+ <div class="w-1/2 flex flex-col bg-gray-100 relative">
85
+ <div class="bg-gray-200 px-4 py-2 border-b border-gray-300 text-xs text-gray-500 flex justify-between items-center">
86
+ <span>实时预览</span>
87
+ <span class="text-xs bg-gray-300 px-2 py-0.5 rounded text-gray-600">Reveal.js Powered</span>
88
+ </div>
89
+ <iframe ref="previewFrame" class="w-full h-full border-none bg-white"></iframe>
90
+
91
+ <!-- Loading Overlay -->
92
+ <div v-if="loading" class="absolute inset-0 bg-white/50 flex items-center justify-center z-20 backdrop-blur-sm">
93
+ <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Help Modal -->
99
+ <div v-if="showHelp" class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" @click.self="showHelp = false">
100
+ <div class="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[80vh] overflow-y-auto">
101
+ <div class="p-6">
102
+ <div class="flex justify-between items-center mb-4">
103
+ <h3 class="text-lg font-bold text-gray-800">Markdown 幻灯片语法指南</h3>
104
+ <button @click="showHelp = false" class="text-gray-400 hover:text-gray-600">
105
+ <i class="fas fa-times"></i>
106
+ </button>
107
+ </div>
108
+ <div class="prose prose-sm prose-indigo text-gray-600">
109
+ <p>Markdown 演示文稿大师使用 Reveal.js 将 Markdown 转换为幻灯片。</p>
110
+
111
+ <h4 class="font-bold text-gray-800 mt-4 mb-2">1. 分页符</h4>
112
+ <ul class="list-disc pl-5 space-y-1">
113
+ <li>使用 <code class="bg-gray-100 px-1 rounded">---</code> (三个横杠) 进行<b>水平</b>分页(新幻灯片)。</li>
114
+ <li>使用 <code class="bg-gray-100 px-1 rounded">--</code> (两个横杠) 进行<b>垂直</b>分页(向下滚动)。</li>
115
+ </ul>
116
+
117
+ <h4 class="font-bold text-gray-800 mt-4 mb-2">2. 基础语法</h4>
118
+ <pre class="bg-gray-800 text-white p-3 rounded text-xs overflow-x-auto">
119
+ # 标题 1 (大标题)
120
+ ## 标题 2 (副标题)
121
+
122
+ - 列表项 1
123
+ - 列表项 2
124
+
125
+ **粗体文字** 和 *斜体文字*
126
+
127
+ > 这是一个引用块
128
+ </pre>
129
+
130
+ <h4 class="font-bold text-gray-800 mt-4 mb-2">3. 代码块</h4>
131
+ <pre class="bg-gray-800 text-white p-3 rounded text-xs overflow-x-auto">
132
+ ```python
133
+ def hello():
134
+ print("Hello World")
135
+ ```
136
+ </pre>
137
+
138
+ <h4 class="font-bold text-gray-800 mt-4 mb-2">4. 演讲者备注</h4>
139
+ <p>使用 <code class="bg-gray-100 px-1 rounded">Note:</code> 开头的行添加备注(仅在演讲者模式可见,按 'S' 键)。</p>
140
+ </div>
141
+ <div class="mt-6 text-right">
142
+ <button @click="showHelp = false" class="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 transition">知道了</button>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <script>
150
+ const { createApp, ref, onMounted, watch } = Vue;
151
+
152
+ createApp({
153
+ setup() {
154
+ const markdown = ref(`# 欢迎使用 Markdown 演示文稿大师
155
+
156
+ 制作精美的幻灯片,从未如此简单。
157
+
158
+ ---
159
+
160
+ ## 为什么选择我们?
161
+
162
+ - ⚡ **极速创作**:专注于内容,而非排版
163
+ - 🎨 **多款主题**:一键切换风格
164
+ - 📱 **移动适配**:随时随地展示
165
+ - 💾 **本地隐私**:数据完全在本地处理
166
+
167
+ --
168
+
169
+ ### 垂直幻灯片示例
170
+
171
+ 这是一个垂直滚动的子幻灯片。
172
+ 使用 \`--\` 分隔。
173
+
174
+ ---
175
+
176
+ ## 代码高亮支持
177
+
178
+ \`\`\`javascript
179
+ function createMagic() {
180
+ console.log("Hello, Presentation!");
181
+ return true;
182
+ }
183
+ \`\`\`
184
+
185
+ ---
186
+
187
+ ## 表格示例
188
+
189
+ | 功能 | 状态 | 优先级 |
190
+ | :--- | :---: | ---: |
191
+ | 导出 HTML | ✅ | 高 |
192
+ | PDF 打印 | ✅ | 中 |
193
+ | 在线分享 | 🚧 | 低 |
194
+
195
+ ---
196
+
197
+ ## 演讲者备注
198
+
199
+ 按 **S** 键打开演讲者视图。
200
+
201
+ Note:
202
+ 这里是演讲者备注。
203
+ 只有你能看到。
204
+ `);
205
+ const currentTheme = ref('black');
206
+ const previewFrame = ref(null);
207
+ const loading = ref(false);
208
+ const showHelp = ref(false);
209
+ let debounceTimer = null;
210
+
211
+ const generateHTML = (md, theme) => {
212
+ return `<!doctype html>
213
+ <html>
214
+ <head>
215
+ <meta charset="utf-8">
216
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
217
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/reset.min.css">
218
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/reveal.min.css">
219
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/theme/${theme}.min.css">
220
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/monokai.min.css">
221
+ <style>
222
+ .reveal .slides { text-align: left; }
223
+ .reveal h1, .reveal h2, .reveal h3 { text-transform: none; }
224
+ </style>
225
+ </head>
226
+ <body>
227
+ <div class="reveal">
228
+ <div class="slides">
229
+ <section data-markdown>
230
+ <textarea data-template>
231
+ ${md}
232
+ </textarea>
233
+ </section>
234
+ </div>
235
+ </div>
236
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/reveal.min.js"><\/script>
237
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/plugin/markdown/markdown.min.js"><\/script>
238
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.5.0/plugin/highlight/highlight.min.js"><\/script>
239
+ <script>
240
+ Reveal.initialize({
241
+ hash: true,
242
+ center: true,
243
+ slideNumber: true,
244
+ plugins: [ RevealMarkdown, RevealHighlight ]
245
+ });
246
+ <\/script>
247
+ </body>
248
+ </html>`;
249
+ };
250
+
251
+ const updatePreview = () => {
252
+ if (!previewFrame.value) return;
253
+ loading.value = true;
254
+
255
+ const doc = previewFrame.value.contentWindow.document;
256
+ doc.open();
257
+ doc.write(generateHTML(markdown.value, currentTheme.value));
258
+ doc.close();
259
+
260
+ setTimeout(() => {
261
+ loading.value = false;
262
+ }, 500);
263
+ };
264
+
265
+ const debouncedUpdate = () => {
266
+ if (debounceTimer) clearTimeout(debounceTimer);
267
+ debounceTimer = setTimeout(updatePreview, 1000);
268
+ };
269
+
270
+ onMounted(() => {
271
+ updatePreview();
272
+ });
273
+
274
+ return {
275
+ markdown,
276
+ currentTheme,
277
+ previewFrame,
278
+ updatePreview,
279
+ debouncedUpdate,
280
+ loading,
281
+ showHelp
282
+ };
283
+ }
284
+ }).mount('#app');
285
+ </script>
286
+ </body>
287
+ </html>