Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,25 @@ docsify serve docs

## 导出 PDF

### 方式一:浏览器打印
在浏览器中打开任意页面,按 `Ctrl+P` / `Cmd+P`,选择"另存为 PDF"。

### 方式二:批量导出(推荐)
使用脚本将整个文档导出为单个 PDF 文件:

```bash
# 安装依赖
npm install puppeteer pdf-lib

# 启动 docsify 服务
docsify serve docs

# 运行导出脚本
node scripts/export-pdf.js
```

输出文件:`claude-code-complete-guide.pdf`

## 源码来源

- [Claude Code CLI](https://github.com/huangserva/claude-code-cli) - TypeScript 源码
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"dependencies": {
"pdf-lib": "^1.17.1",
"puppeteer": "^24.40.0"
}
}
48 changes: 48 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# PDF 导出脚本

## 依赖安装

```bash
npm install puppeteer pdf-lib
```

> 注意: Puppeteer 需要下载 Chromium(约 150MB),国内可能较慢。可使用镜像:
> ```bash
> PUPPETEER_DOWNLOAD_HOST=https://npmmirror.com/mirrors npm install puppeteer
> ```

## 使用方法

### 1. 启动 docsify 服务

终端 1:
```bash
docsify serve docs
```

### 2. 运行导出脚本

终端 2:
```bash
node scripts/export-pdf.js
```

## 配置修改

编辑 `scripts/export-pdf.js` 中的 `CONFIG` 对象:

```javascript
const CONFIG = {
baseUrl: 'http://localhost:3000', // 修改为你的服务地址
outputPath: './claude-code-complete-guide.pdf', // 输出路径
tempDir: './temp-pdfs',
waitTime: 2000, // 页面渲染等待时间(ms)
pageTimeout: 30000, // 单页超时(ms)
};
```

## 输出

- 生成单个 PDF 文件,包含所有 114 页内容
- 自动添加页码
- 保留原文档样式和图片
202 changes: 202 additions & 0 deletions scripts/export-pdf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/**
* Claude Code 完全指南 - PDF 导出脚本
*
* 使用方法:
* 1. 先启动 docsify 服务: docsify serve docs
* 2. 安装依赖: npm i puppeteer pdf-lib
* 3. 运行: node scripts/export-pdf.js
*/

const puppeteer = require('puppeteer');
const { PDFDocument } = require('pdf-lib');
const fs = require('fs');
const path = require('path');
const https = require('https');
const http = require('http');

// 等待函数(兼容新版 Puppeteer)
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// ============ 配置 ============
// 注意: 如果 docsify 不是 3000 端口,请修改 baseUrl
const CONFIG = {
baseUrl: 'http://localhost:52861', // 修改为你的 docsify 端口
outputPath: './claude-code-complete-guide.pdf',
tempDir: './temp-pdfs',
waitTime: 2000, // 页面渲染等待时间(ms)
pageTimeout: 30000, // 单页超时(ms)
};

// ============ 解析侧边栏获取所有页面 ============
function parseSidebar() {
const sidebarPath = path.join(__dirname, '..', 'docs', '_sidebar.md');
const content = fs.readFileSync(sidebarPath, 'utf-8');
const pages = [];

// 匹配所有 markdown 链接 [标题](路径)
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let match;

while ((match = linkRegex.exec(content)) !== null) {
const title = match[1];
let url = match[2];

// 跳过外部链接和特殊路径
if (url.startsWith('http') || url.startsWith('//') || url === '/') {
continue;
}

// 转换为 docsify 路由格式 (#/path/to/page)
// 移除 .md 扩展名
url = url.replace(/\.md$/, '');
// 添加 #/ 前缀
const route = '#/' + url;

pages.push({ title, route });
}

return pages;
}

// ============ 确保目录存在 ============
function ensureDir(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}

// ============ 下载单个页面 PDF ============
async function downloadPagePdf(page, browser, index, total) {
const url = `${CONFIG.baseUrl}/${page.route}`;
console.log(`[${index + 1}/${total}] 正在处理: ${page.title}`);

try {
const pageObj = await browser.newPage();

// 设置视口
await pageObj.setViewport({
width: 1200,
height: 800,
deviceScaleFactor: 2, // 2x 分辨率更清晰
});

// 访问页面
await pageObj.goto(url, {
waitUntil: 'networkidle0',
timeout: CONFIG.pageTimeout,
});

// 等待内容加载
await wait(CONFIG.waitTime);

// 生成 PDF
const pdfBytes = await pageObj.pdf({
format: 'A4',
printBackground: true,
margin: {
top: '20mm',
right: '15mm',
bottom: '20mm',
left: '15mm',
},
displayHeaderFooter: true,
headerTemplate: `
<div style="font-size: 10px; width: 100%; text-align: right; margin-bottom: 10px; color: #666;">
<span class="title"></span>
</div>
`,
footerTemplate: `
<div style="font-size: 10px; width: 100%; text-align: center; color: #666;">
<span class="pageNumber"></span> / <span class="totalPages"></span>
</div>
`,
});

await pageObj.close();

return {
title: page.title,
pdfBytes: Buffer.from(pdfBytes),
};
} catch (error) {
console.error(` ❌ 错误: ${error.message}`);
return null;
}
}

// ============ 合并 PDF ============
async function mergePdfs(pdfBuffers) {
console.log('\n正在合并 PDF...');

const mergedPdf = await PDFDocument.create();

for (const { title, pdfBytes } of pdfBuffers) {
try {
const pdf = await PDFDocument.load(pdfBytes);
const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
pages.forEach(page => mergedPdf.addPage(page));
console.log(` ✓ 添加: ${title}`);
} catch (error) {
console.error(` ❌ 合并失败 ${title}: ${error.message}`);
}
}

const finalBytes = await mergedPdf.save();
fs.writeFileSync(CONFIG.outputPath, finalBytes);

console.log(`\n✅ PDF 已导出: ${CONFIG.outputPath}`);
console.log(` 文件大小: ${(finalBytes.length / 1024 / 1024).toFixed(2)} MB`);
console.log(` 总页数: ${mergedPdf.getPageCount()}`);
}

// ============ 主流程 ============
async function main() {
console.log('='.repeat(50));
console.log('Claude Code 完全指南 - PDF 导出工具');
console.log('='.repeat(50));

// 1. 解析页面列表
console.log('\n[1/4] 解析页面列表...');
const pages = parseSidebar();
console.log(`找到 ${pages.length} 个页面`);

// 2. 清理临时目录
console.log('\n[2/4] 准备临时目录...');
ensureDir(CONFIG.tempDir);
fs.readdirSync(CONFIG.tempDir).forEach(file => {
fs.unlinkSync(path.join(CONFIG.tempDir, file));
});

// 3. 启动浏览器并下载所有页面
console.log('\n[3/4] 启动浏览器...');
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});

const pdfBuffers = [];

for (let i = 0; i < pages.length; i++) {
const result = await downloadPagePdf(pages[i], browser, i, pages.length);
if (result) {
pdfBuffers.push(result);
}
}

await browser.close();

// 4. 合并 PDF
console.log('\n[4/4] 生成最终 PDF...');
await mergePdfs(pdfBuffers);

// 清理临时文件
fs.rmSync(CONFIG.tempDir, { recursive: true, force: true });

console.log('\n完成!');
}

// ============ 运行 ============
main().catch(error => {
console.error('导出失败:', error);
process.exit(1);
});