内容导入
EmDash 的导入系统采用可插拔的源架构。每个源都知道如何从特定平台探测、分析和获取内容。
| 源 ID | 平台 | 探测 | OAuth | 完整导入 |
|---|---|---|---|---|
wxr | WordPress 导出文件 | 否 | 否 | 是 |
wordpress-com | WordPress.com | 是 | 是 | 是 |
wordpress-rest | 自托管 WordPress | 是 | 否 | 仅探测 |
WXR 文件上传
Section titled “WXR 文件上传”最完整的导入方法。直接将 WordPress 扩展 RSS (WXR) 导出文件上传到管理仪表板。
功能:
- 所有文章类型(包括自定义类型)
- 所有元字段
- 草稿和私密文章
- 完整的分类层级
- 媒体附件元数据
如何获取 WXR 文件:
- 在 WordPress 管理面板,转到 工具 → 导出
- 选择 所有内容 或特定文章类型
- 点击 下载导出文件
- 将
.xml文件上传到 EmDash
WordPress.com OAuth
Section titled “WordPress.com OAuth”对于托管在 WordPress.com 上的站点,通过 OAuth 连接进行导入,无需手动导出文件。
- 输入您的 WordPress.com 站点 URL
- 点击 使用 WordPress.com 连接
- 在 WordPress.com 弹出窗口中授权 EmDash
- 选择要导入的内容
包含内容:
- 已发布和草稿内容
- 私密文章(需授权)
- 通过 API 获取的媒体文件
- 暴露给 REST API 的自定义字段
WordPress REST API 探测
Section titled “WordPress REST API 探测”当您输入一个 URL 时,EmDash 会探测该站点以检测是否为 WordPress 并显示可用内容:
Detected: WordPress 6.4├── Posts: 127 (published)├── Pages: 12 (published)└── Media: 89 files
Note: Drafts and private content require authenticationor a full WXR export.REST 探测仅用于信息展示。对于完整导入,它会建议上传 WXR 文件或通过 OAuth 连接(针对 WordPress.com)。
所有源都遵循相同的流程:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Connect │────▶│ Analyze │────▶│ Prepare │────▶│ Execute ││ (probe/ │ │ (schema │ │ (create │ │ (import ││ upload) │ │ check) │ │ schema) │ │ content) │└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘步骤 1:连接
Section titled “步骤 1:连接”输入 URL 进行探测或直接上传文件。
URL 探测 会并行运行所有已注册的源。匹配置信度最高的结果将决定建议的下一步操作:
- WordPress.com 站点 → 提供 OAuth 连接
- 自托管 WordPress → 显示导出说明
- 未知 → 建议文件上传
步骤 2:分析
Section titled “步骤 2:分析”源解析内容并检查模式兼容性:
Post Types:├── post (127) → posts [New collection]├── page (12) → pages [Existing, compatible]├── product (45) → products [Add 3 fields]└── revision (234) → [Skip - internal type]
Required Schema Changes:├── Create collection: posts├── Add fields to pages: featured_image└── Create collection: products每种文章类型显示其状态:
| 状态 | 含义 |
|---|---|
| 就绪 | 集合已存在且字段兼容 |
| 新集合 | 将自动创建 |
| 添加字段 | 集合已存在,将添加缺失字段 |
| 不兼容 | 字段类型冲突(需要手动修复) |
步骤 3:准备模式
Section titled “步骤 3:准备模式”点击 创建模式并导入 以:
- 通过 SchemaRegistry 创建新集合
- 添加具有正确列类型的缺失字段
- 设置带有索引的内容表
步骤 4:执行导入
Section titled “步骤 4:执行导入”内容按顺序导入:
- Gutenberg/HTML 转换为 Portable Text
- WordPress 状态映射到 EmDash 状态
- WordPress 作者映射到所有权 (
authorId) 和展示署名 - 创建并链接分类法
- 可重用区块 (
wp_block) 作为 Sections 导入 - 实时显示进度
作者导入行为:
- 如果作者映射指向一个 EmDash 用户,则所有权将设置为该用户,并为同一用户创建/重用链接署名。
- 如果没有用户映射,则根据 WordPress 作者身份创建/重用访客署名。
- 导入的条目获得有序的署名归属,第一个署名被设置为
primaryBylineId。
步骤 5:媒体导入(可选)
Section titled “步骤 5:媒体导入(可选)”内容导入后,可选择导入媒体:
-
分析 — 按类型显示附件数量
Media found:├── Images: 75 files├── Video: 10 files└── Other: 4 files -
下载 — 从 WordPress URL 流式传输并显示进度
Importing media...├── 45 of 89 (50%)├── Current: vacation-photo.jpg└── Status: Uploading -
重写 URL — 内容自动更新为新 URL
媒体导入使用内容哈希 (xxHash64) 进行去重。同一张图片在多篇文章中使用时只存储一次。
导入源实现标准接口:
interface ImportSource { /** Unique identifier */ id: string;
/** Display name */ name: string;
/** Probe a URL (optional) */ probe?(url: string): Promise<SourceProbeResult | null>;
/** Analyze content from this source */ analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis>;
/** Stream content items */ fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem>;}源接受不同的输入类型:
// File upload (WXR){ type: "file", file: File }
// URL with optional token (REST API){ type: "url", url: string, token?: string }
// OAuth connection (WordPress.com){ type: "oauth", url: string, accessToken: string }所有源都产生相同的标准化格式:
interface NormalizedItem { sourceId: string | number; postType: string; status: "publish" | "draft" | "pending" | "private" | "future"; slug: string; title: string; content: PortableTextBlock[]; excerpt?: string; date: Date; author?: string; authors?: string[]; categories?: string[]; tags?: string[]; meta?: Record<string, unknown>; featuredImage?: string;}API 端点
Section titled “API 端点”导入系统暴露以下端点:
探测 URL
Section titled “探测 URL”POST /_emdash/api/import/probeContent-Type: application/json
{ "url": "https://example.com" }返回检测到的平台和建议的操作。
分析 WXR
Section titled “分析 WXR”POST /_emdash/api/import/wordpress/analyzeContent-Type: multipart/form-data
file: [WordPress export .xml]返回文章类型分析及模式兼容性。
POST /_emdash/api/import/wordpress/prepareContent-Type: application/json
{ "postTypes": [ { "name": "post", "collection": "posts", "enabled": true } ]}创建集合和字段。
POST /_emdash/api/import/wordpress/executeContent-Type: multipart/form-data
file: [WordPress export .xml]config: { "postTypeMappings": { "post": { "collection": "posts" } } }将内容导入到指定的集合。
POST /_emdash/api/import/wordpress/mediaContent-Type: application/json
{ "attachments": [{ "id": 123, "url": "https://..." }], "stream": true}在下载/上传过程中流式传输 NDJSON 进度更新。
重写 URL
Section titled “重写 URL”POST /_emdash/api/import/wordpress/rewrite-urlsContent-Type: application/json
{ "urlMap": { "https://old.com/image.jpg": "/_emdash/media/abc123" }}使用新的媒体 URL 更新 Portable Text 内容。
- 网络超时 — 使用退避策略重试
- 单个项目解析失败 — 记录日志,跳过,导入继续
- 媒体下载失败 — 标记为需要手动处理
- 无效的文件格式 — 导入停止并显示错误信息
- 数据库连接丢失 — 导入暂停,允许恢复
- 存储配额超出 — 导入停止,显示使用情况
导入后:
Import Complete
✓ 125 posts imported✓ 12 pages imported✓ 85 media references recorded
⚠ 2 items had warnings: - Post "Special Characters ñ" - title encoding fixed - Page "About" - duplicate slug renamed to "about-1"
✗ 1 item failed: - Post ID 456 - content parsing error (saved as draft)失败的项目将保存为草稿,原始内容保存在 _importError 中供审查。
构建自定义源
Section titled “构建自定义源”为其他平台创建源:
import type { ImportSource } from "emdash/import";
export const mySource: ImportSource = { id: "my-platform", name: "My Platform", description: "Import from My Platform", icon: "globe", canProbe: true,
async probe(url) { // Check if URL matches your platform const response = await fetch(`${url}/api/info`); if (!response.ok) return null;
return { sourceId: "my-platform", confidence: "definite", detected: { platform: "my-platform" }, // ... }; },
async analyze(input, context) { // Parse and analyze content // Return ImportAnalysis },
async *fetchContent(input, options) { // Yield NormalizedItem for each content piece for (const item of items) { yield { sourceId: item.id, postType: "post", title: item.title, content: convertToPortableText(item.body), // ... }; } },};在您的 EmDash 配置中注册该源:
import { mySource } from "./src/import/custom-source";
export default defineConfig({ integrations: [ emdash({ import: { sources: [mySource], }, }), ],});- WordPress 迁移 — 完整的 WordPress 迁移指南
- 插件移植 — 将 WordPress 插件移植到 EmDash