21.内容拓展:通过前端JS动态生成并下载.md文件
发表于更新于
字数总计:1.6k阅读时长:7分钟阅读量: 广东
21.内容拓展:通过前端JS动态生成并下载.md文件
因为静态博客没有后端,我们不能直接提供源文件的下载链接(这会暴露您的整个 source
目录,不安全)。所以,我们将采用一种更聪明的、纯前端的解决方案。
工作原理:
- 在Hexo生成文章页面时,我们将该文章的所有元数据(Front-matter)和Markdown正文内容,作为一个数据块(JSON格式)嵌入到页面的HTML中。
- 我们在页面上放置一个“下载源文件”的按钮。
- 当用户点击按钮时,一段JavaScript代码会被触发。它会读取页面中嵌入的数据,在浏览器中重新拼接出与您原始
.md
文件一模一样的内容。 - 最后,JS会创建一个虚拟的下载链接,让用户将这个拼接好的内容保存为
.md
文件。
这个方案既能满足需求,又非常安全,不会暴露您的任何额外信息。
第一步:创建核心JavaScript文件
我们需要修改主题的模板,将每篇文章的数据输出到HTML中。
- 新增核心的JS逻辑
- 文件路径:
themes/anzhiyu/source/custom/js/markdown-download.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
(function() { 'use strict';
function formatFrontMatterToYAML(data) { let yamlString = '---\n'; yamlString += `title: ${data.title}\n`; yamlString += `date: ${data.date}\n`; if (data.updated && data.updated !== data.date) { yamlString += `updated: ${data.updated}\n`; } if (data.categories && data.categories.length > 0) { yamlString += 'categories:\n'; data.categories.forEach(cat => { yamlString += ` - ${cat}\n`; }); } if (data.tags && data.tags.length > 0) { yamlString += 'tags:\n'; data.tags.forEach(tag => { yamlString += ` - ${tag}\n`; }); } yamlString += '---\n\n'; return yamlString; }
function sanitizeFilename(filename) { return filename.replace(/[<>:"/\\|?*]/g, '_') .replace(/\s+/g, '_') .replace(/_{2,}/g, '_') .replace(/^_|_$/g, ''); }
function downloadFile(content, filename) { try { const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); return true; } catch (error) { return false; } }
function validatePostData(postData) { if (!postData) { return { valid: false, message: '文章数据不存在' }; } if (!postData.title) { return { valid: false, message: '文章标题缺失' }; } if (!postData.content) { return { valid: false, message: '文章内容缺失' }; } if (!postData.date) { return { valid: false, message: '文章日期缺失' }; } return { valid: true, message: '数据验证通过' }; }
function handleDownloadClick() { try { const postData = window.postData; const validation = validatePostData(postData); if (!validation.valid) { throw new Error(validation.message); } const frontMatter = formatFrontMatterToYAML(postData); const fullContent = frontMatter + postData.content; const safeFilename = sanitizeFilename(postData.title) + '.md'; const downloadSuccess = downloadFile(fullContent, safeFilename); if (downloadSuccess) { if (window.anzhiyu && window.anzhiyu.snackbarShow) { window.anzhiyu.snackbarShow('📄 Markdown文件下载成功!'); } } else { throw new Error('文件下载过程中发生错误'); } } catch (error) { alert(`下载失败: ${error.message}\n\n请检查浏览器控制台获取更多技术信息。`); } }
function initArticleDownload() { const downloadBtn = document.getElementById('download-md-btn'); const hasPostData = window.postData; if (downloadBtn && hasPostData) { downloadBtn.removeEventListener('click', handleDownloadClick); downloadBtn.addEventListener('click', handleDownloadClick); } }
function onPageReady() { setTimeout(initArticleDownload, 100); }
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', onPageReady); } else { onPageReady(); }
document.addEventListener('pjax:success', onPageReady);
window.MarkdownDownload = { init: initArticleDownload, download: handleDownloadClick, version: '1.0.0' };
})();
|
第二步:修改文章页面模板
文件路径: themes/anzhiyu/layout/post.pug
操作: 在文件末尾添加以下代码
1 2 3 4 5 6 7 8 9 10
| //- 文章源文件下载功能:嵌入文章数据 script. window.postData = { title: "#{page.title || ''}", date: "#{page.date ? page.date.format('YYYY-MM-DD HH:mm:ss') : ''}", updated: "#{page.updated ? page.updated.format('YYYY-MM-DD HH:mm:ss') : (page.date ? page.date.format('YYYY-MM-DD HH:mm:ss') : '')}", tags: !{JSON.stringify(page.tags ? page.tags.map(tag => tag.name) : [])}, categories: !{JSON.stringify(page.categories ? page.categories.map(cat => cat.name) : [])}, content: !{JSON.stringify((page._content || '').replace(/<\/script>/gi, '<\\/script>').replace(/<\/style>/gi, '<\\/style>'))} };
|
第三步:添加下载按钮
文件路径: themes/anzhiyu/layout/includes/rightside.pug
操作1: 找到 when 'comment'
部分,在其后添加:
1 2 3 4
| when 'downloadMd' if is_post() button#download-md-btn(type="button" title="下载文章源文件") i.anzhiyufont.anzhiyu-icon-download
|
操作2: 找到 showArray
定义行,修改为:
1
| - const showArray = enable ? show && show.split(',') : ['toc','chat','comment','downloadMd']
|
第四步:配置文件引用
文件路径: _config.anzhiyu.yml
操作: 在 inject.bottom
部分添加
1 2
| bottom: - '<script src="/custom/js/markdown-download.js"></script>'
|