这个阶段的目标是搭建一个基础可用的编辑器页面,集成 Milkdown,并实现核心的“延迟上传”图片处理逻辑。
app 目录下创建一个新的路由用于文章创作,例如 app/create/page.tsx。title) 的 <input> 组件。description) 的 <textarea> 组件。tags) 的组件(例如,一个简单的、用逗号分隔的文本输入框)。<input> 组件。<div> 容器。components/Editor.tsx,并在其文件顶部声明 "use client;",因为 Milkdown 是一个纯客户端组件。@milkdown/crepe 编辑器。onUpload 回调函数。当用户粘贴、拖拽或选择图片时,此函数被触发。File 对象。URL.createObjectURL(file) 生成一个本地的 blob: URL。Map<string, File> 对象,将 blob: URL 和原始 File 对象关联起来并存储。blob: URL 返回给 Milkdown,编辑器会立即显示图片,实现“即时”预览效果。useEffect 的 cleanup 函数,在组件卸载时调用 URL.revokeObjectURL() 来释放内存,避免内存泄漏。/create) 将成为一个受保护的路由。未登录的用户访问时,将被重定向到登录页。page.tsx 中获取服务端 session 来实现。这个阶段的核心是实现从浏览器到 Cloudflare R2 的安全上传,并将文章内容中的 blob: URL 替换为最终的线上地址。
app/api/upload/route.ts 创建一个新的 API 路由。POST 请求,其核心职责是生成用于上传到 Cloudflare R2 的预签名 URL (Presigned URL)。auth() 辅助函数对该 API 进行保护,确保只有已登录的用户才能请求上传链接。users/{userId}/{document-slug}/{timestamp}-{filename}。这确保了文件的归属清晰,便于未来管理。@aws-sdk/s3-request-presigner 和 @aws-sdk/client-s3 (已配置为指向 R2) 生成一个有时效性的 PutObject 预签名 URL。Map<blobUrl, File>。File 对象:
/api/upload 接口,获取 R2 的预签名 URL 和最终的公开 URL。fetch API,以 PUT 方法将 File 对象直接上传到获取到的预签名 URL。Promise.all 等待所有图片都上传完毕。blob: URL 批量替换成它们对应的最终公开 URL。此阶段的核心是将在编辑器中创作的内容与现有基于 GitHub URL 的投稿流程进行整合。现有系统并非通过后端 API 提交,而是引导用户到 GitHub 网站上完成操作。
blob: URL 也被替换为线上 URL 后,点击“发布”按钮的最终操作将触发一个与现有 Contribute.tsx 组件类似的流程。Contribute.tsx 中的目录选择对话框,让用户选择新文章在 app/docs/ 目录下的存放位置。date) 生成标准的 Frontmatter 字符串。buildGithubNewUrl 函数(该函数已存在于 Contribute.tsx 中),将用户选择的目录路径、文件名以及 Base64 编码后的完整文件内容作为参数,生成一个指向 GitHub 新建文件页面的 URL。window.open 打开这个新生成的 URL。CONTRIBUTING.md 指南,在 GitHub 上手动完成后续的 Commit changes 和创建 Pull Request (PR) 的步骤。这个方案将整个流程分解为三个清晰的阶段,优先实现核心的编辑器体验,然后是关键的图片上传,最后与现有系统集成。采用预签名 URL 的方式,可以最大程度地减轻 Vercel Serverless Function 的负载,提升上传性能和安全性,非常适合该项目的技术栈和部署环境。