markdown-bianjiqi.html 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Markdown Editor</title>
  7. <style>
  8. :root {
  9. --border-color: #eee;
  10. --bg-color: #fafafa;
  11. --text-color: #333;
  12. --btn-border: #ddd;
  13. --code-bg: #f5f5f5;
  14. --scrollbar-color: #ccc;
  15. }
  16. * {
  17. margin: 0;
  18. padding: 0;
  19. box-sizing: border-box;
  20. }
  21. body {
  22. height: 100vh;
  23. display: flex;
  24. flex-direction: column;
  25. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  26. overflow: hidden;
  27. }
  28. .header {
  29. padding: 8px 20px;
  30. background: var(--bg-color);
  31. border-bottom: 1px solid var(--border-color);
  32. display: flex;
  33. justify-content: flex-end;
  34. align-items: center;
  35. font-size: 14px;
  36. color: var(--text-color);
  37. }
  38. .toolbar {
  39. display: flex;
  40. gap: 8px;
  41. }
  42. .btn {
  43. border: 1px solid var(--btn-border);
  44. background: white;
  45. padding: 4px 12px;
  46. border-radius: 4px;
  47. cursor: pointer;
  48. font-size: 14px;
  49. color: var(--text-color);
  50. min-width: 80px;
  51. text-align: center;
  52. transition: background-color 0.2s ease;
  53. }
  54. .btn:hover {
  55. background: var(--code-bg);
  56. }
  57. .content {
  58. display: flex;
  59. flex: 1;
  60. overflow: hidden;
  61. }
  62. #preview, #editor {
  63. width: 50%;
  64. height: 100%;
  65. padding: 20px;
  66. line-height: 1.6;
  67. }
  68. #preview {
  69. color: var(--text-color);
  70. border-right: 1px solid var(--border-color);
  71. overflow-y: auto;
  72. }
  73. #editor {
  74. font-family: monospace;
  75. font-size: 14px;
  76. border: none;
  77. outline: none;
  78. resize: none;
  79. overflow-y: auto;
  80. scrollbar-width: thin;
  81. scrollbar-color: var(--scrollbar-color) transparent;
  82. }
  83. #editor::-webkit-scrollbar {
  84. width: 8px;
  85. }
  86. #editor::-webkit-scrollbar-thumb {
  87. background-color: var(--scrollbar-color);
  88. border-radius: 4px;
  89. }
  90. #editor::-webkit-scrollbar-track {
  91. background-color: transparent;
  92. }
  93. .preview-content {
  94. height: auto;
  95. min-height: 100%;
  96. padding-right: 8px;
  97. }
  98. #preview h1, #preview h2, #preview h3 {
  99. margin: 16px 0;
  100. font-weight: 500;
  101. }
  102. #preview h1:first-child {
  103. margin-top: 0;
  104. }
  105. #preview p {
  106. margin: 12px 0;
  107. }
  108. #preview ul, #preview ol {
  109. padding-left: 24px;
  110. margin: 12px 0;
  111. }
  112. #preview code {
  113. background-color: var(--code-bg);
  114. padding: 2px 4px;
  115. border-radius: 3px;
  116. font-size: 0.9em;
  117. }
  118. #preview pre code {
  119. display: block;
  120. padding: 12px;
  121. overflow-x: auto;
  122. line-height: 1.45;
  123. }
  124. .toast {
  125. position: fixed;
  126. top: 20px;
  127. right: 20px;
  128. background: rgba(0, 0, 0, 0.8);
  129. color: white;
  130. padding: 8px 16px;
  131. border-radius: 4px;
  132. font-size: 14px;
  133. display: none;
  134. z-index: 1000;
  135. animation: fadeIn 0.3s ease;
  136. }
  137. @keyframes fadeIn {
  138. from { opacity: 0; transform: translateY(-10px); }
  139. to { opacity: 1; transform: translateY(0); }
  140. }
  141. </style>
  142. </head>
  143. <body>
  144. <div class="header">
  145. <div class="toolbar">
  146. <button class="btn" id="copyBtn">一键复制</button>
  147. <button class="btn" id="exportBtn">导出文本</button>
  148. </div>
  149. </div>
  150. <div class="content">
  151. <div id="preview">
  152. <div class="preview-content"></div>
  153. </div>
  154. <textarea id="editor" placeholder="在这里输入 Markdown 文本..."></textarea>
  155. </div>
  156. <div id="toast" class="toast"></div>
  157. <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  158. <script>
  159. document.addEventListener('DOMContentLoaded', function() {
  160. const editor = document.getElementById('editor');
  161. const preview = document.getElementById('preview');
  162. const previewContent = preview.querySelector('.preview-content');
  163. const toast = document.getElementById('toast');
  164. const copyBtn = document.getElementById('copyBtn');
  165. const exportBtn = document.getElementById('exportBtn');
  166. // Initialize marked with safe options
  167. marked.setOptions({
  168. breaks: true,
  169. gfm: true,
  170. headerIds: true,
  171. sanitize: false,
  172. highlight: function(code, lang) {
  173. return code;
  174. }
  175. });
  176. function showToast(message, duration = 2000) {
  177. toast.textContent = message;
  178. toast.style.display = 'block';
  179. setTimeout(() => {
  180. toast.style.display = 'none';
  181. }, duration);
  182. }
  183. function updatePreview() {
  184. try {
  185. const markdownText = editor.value;
  186. const htmlContent = marked.parse(markdownText);
  187. previewContent.innerHTML = htmlContent;
  188. } catch (error) {
  189. console.error('Markdown parsing error:', error);
  190. previewContent.innerHTML = '<p style="color: red;">Error parsing markdown</p>';
  191. }
  192. }
  193. function debounce(func, wait) {
  194. let timeout;
  195. return function(...args) {
  196. clearTimeout(timeout);
  197. timeout = setTimeout(() => func.apply(this, args), wait);
  198. };
  199. }
  200. const debouncedUpdate = debounce(updatePreview, 150);
  201. async function copyText() {
  202. try {
  203. await navigator.clipboard.writeText(editor.value);
  204. showToast('已复制到剪贴板');
  205. } catch (err) {
  206. console.error('Copy failed:', err);
  207. showToast('复制失败');
  208. }
  209. }
  210. function exportTxt() {
  211. try {
  212. const text = editor.value;
  213. const blob = new Blob([text], { type: 'text/plain' });
  214. const url = URL.createObjectURL(blob);
  215. const a = document.createElement('a');
  216. const timestamp = new Date().toISOString().slice(0,19).replace(/[-:]/g, '');
  217. a.href = url;
  218. a.download = `markdown_${timestamp}.txt`;
  219. document.body.appendChild(a);
  220. a.click();
  221. document.body.removeChild(a);
  222. URL.revokeObjectURL(url);
  223. showToast('已导出为文本文件');
  224. } catch (err) {
  225. console.error('Export failed:', err);
  226. showToast('导出失败');
  227. }
  228. }
  229. // Event Listeners
  230. editor.addEventListener('input', debouncedUpdate);
  231. copyBtn.addEventListener('click', copyText);
  232. exportBtn.addEventListener('click', exportTxt);
  233. // Sync scroll
  234. editor.addEventListener('scroll', () => {
  235. const percentage = editor.scrollTop / (editor.scrollHeight - editor.clientHeight);
  236. preview.scrollTop = percentage * (preview.scrollHeight - preview.clientHeight);
  237. });
  238. // Initial content
  239. const initialText = `# Markdown 编辑器
  240. ## 欢迎使用!
  241. 这是一个简单的 Markdown 编辑器。
  242. ### 主要功能:
  243. 1. 实时预览
  244. 2. 一键复制内容
  245. 3. 导出文本文件
  246. 4. 支持常用 Markdown 语法
  247. ### 快速开始:
  248. - 在右侧输入 Markdown 文本
  249. - 在左侧查看实时预览
  250. - 使用顶部按钮进行操作
  251. \`\`\`markdown
  252. # 示例代码
  253. **粗体** 和 *斜体*
  254. - 列表项
  255. - 另一个列表项
  256. \`\`\``;
  257. editor.value = initialText;
  258. updatePreview();
  259. });
  260. </script>
  261. </body>
  262. </html>