mcpField
C
course-ebook-publishing

course-ebook-publishing

github.com/kevintsai1202文档处理5277 次安装24 周前更新
代码仓库

关于 course-ebook-publishing

用于网站完成后的课程电子书制作技能,可将 `window.COURSE` 数据转换为 PDF,并可选生成 DOCX。它以同一份 Markdown 源内容构建两种格式,保持内容一致,并排除测验及仅适用于交互的内容。

主要功能

  1. 01读取 `window.COURSE` 课程数据
  2. 02合成共用的 Markdown 源文件
  3. 03通过 Playwright 生成 PDF
  4. 04可选用 pandoc 生成 DOCX
  5. 05过滤测验和仅交互内容
  6. 06提供成品验证指南

适用场景

  1. 01制作可打印的课程讲义
  2. 02归档课程内容
  3. 03网站完成后制作客户交付文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kevintsai1202/teaching-site-skills --skill course-ebook-publishing

安装后在 Claude Code 中输入/course-ebook-publishing确认已加载。

详细介绍

课程电子书出版

架构权威来源:此技能读取实时的 window.COURSE 对象,其结构定义于 _shared/domain-primitives.md。测验/前测/后测题目会从电子书中筛除(遵循第 10 节测验题目规则及电子书内容政策)。

文件名约定(英文优先):输出文件位于 dist/{name}.pdf / dist/{name}.docx。源 Markdown 在 dist/master.md 中编排。

此技能会将已完成的教学网站转换为书籍——以 PDF 为主,也可选择 DOCX(供编辑审阅或进一步创作)。这是一个建站后步骤:它使用实时网站中的 window.COURSE,不会重新编写内容。

何时调用

  • 网站功能已完备(第 1–5 阶段已完成),且内容稳定。
  • 利益相关方需要可打印的讲义、可归档的记录,或用于非 Web 渠道的交付物。
  • 有时会在 course-corporate-edition 之后调用,以生成客户交付包。

网站处于开发中时,切勿调用。电子书流水线会读取 window.COURSE;如果网站每天都在变化,电子书也会每天偏离。

架构:单一来源 → 两种输出

window.COURSE  ─┐
                ├─→ compose-ebook.mjs ─→ master.md ─→ pandoc ─→ ebook.docx
asset folders ─┤                                  └─→ Playwright page.pdf() ─→ ebook.pdf
materials/   ─┘

为何使用单一的 Markdown 中间文件:确保 PDF 和 DOCX 内容一致。Pandoc 可以顺畅地将 Markdown 转换为 DOCX;Playwright 则通过 HTML 包装器将相同的 Markdown 渲染为 PDF。两种输出,一个事实来源。

文件布局(标准)

scripts/
├── build-ebook.mjs              ← CLI entry point (--md-only, --no-docx, --no-pdf, --output, --keep-html)
├── render-pdf.mjs               ← markdown → HTML → PDF via Playwright
└── lib/
    ├── compose-ebook.mjs        ← loadSources + composeXxx functions (cover, TOC, chapters, appendix)
    └── reference.docx           ← pandoc style template (generated by gen-reference-docx.mjs)

style-ebook.css                   ← @page rules, cover, page-number footer, print-only styles
dist/                             ← output: ebook.md, ebook.pdf, ebook.docx

加载源数据:从 index.html 获取 window.COURSE

网站使用外部 course-data.js 时,直接导入即可。使用内联 COURSE(企业版)时,则通过正则表达式和 vm 沙箱提取:

import vm from 'node:vm';
async function loadCourseFromIndexHtml(htmlPath) {
  const html = await fs.readFile(htmlPath, 'utf8');
  const match = html.match(/<script>\s*window\.COURSE\s*=\s*\{[\s\S]*?\};\s*<\/script>/);
  if (!match) throw new Error('window.COURSE 區塊找不到');
  const code = match[0].replace(/<\/?script>/g, '');
  const sandbox = { window: {}, console };
  vm.createContext(sandbox);
  vm.runInContext(code, sandbox);
  return sandbox.window.COURSE;
}

使用 vm 沙箱而非 eval:vm 会将脚本的全局变量与构建进程隔离,避免意外副作用造成影响。

PDF 渲染:使用 Playwright page.pdf()(而非 Chrome CLI)

原始实现:pandoc → HTML → msedge --headless --print-to-pdf。改用 Playwright——Chrome CLI 不支持 footerTemplate(无法添加页码)。

import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(`file://${path.resolve(htmlPath)}`, { waitUntil: 'networkidle' });
await page.pdf({
  path: outputPath,
  format: 'A4',
  printBackground: true,
  displayHeaderFooter: true,
  headerTemplate: '<div></div>',
  footerTemplate: `
    <div style="font-size:9pt; width:100%; text-align:center; color:#666;">
      <span class="pageNumber"></span> / <span class="totalPages"></span>
    </div>`,
  margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
});
await browser.close();
不显示页码的封面页

封面不应显示页码。CSS:

@page { margin: 20mm; }
@page :first { margin: 0; }    /* full bleed cover */

Playwright 会遵循 @page :first——封面页不会打印页脚。

书籍风格布局(style-ebook.css)

示例工作坊中“书籍级排版”的提交,主要由美观需求驱动,而非仅为正式。以下关键规则值得移植:

/* 章首跨頁分頁 */
h1.chapter { page-break-before: always; }

/* 圖文並排(Grid 排版) */
.figure-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12mm;
}
.figure-grid .three-up { grid-template-columns: repeat(3, 1fr); }

/* 提示詞區塊:橘色 PROMPT 徽章 + 暗底 code */
.prompt-block {
  background: #1a1a1a; color: #e0e0e0;
  border-left: 4px solid #ff8c00;
  padding: 12pt; margin: 8pt 0;
}
.prompt-block::before {
  content: 'PROMPT'; display: inline-block;
  background: #ff8c00; color: white;
  padding: 2pt 8pt; border-radius: 3pt;
  font-size: 8pt; margin-bottom: 6pt;
}

/* 任務勾選方框(純印刷) */
.task::before {
  content: '☐'; margin-right: 8pt;
  font-size: 14pt; color: #888;
}

/* 學習目標綠色徽章 */
.goal::before {
  content: '✓'; margin-right: 6pt;
  color: #2e7d32; font-weight: bold;
}

通过 pandoc + 参考模板生成 DOCX

const args = [
  mdPath,
  '-f', 'gfm+attributes+raw_html',
  '-t', 'docx',
  '--standalone',
  '--toc', '--toc-depth=2',
  '--resource-path', path.dirname(mdPath),
  '--reference-doc', 'scripts/lib/reference.docx',
  '-o', outPath,
];
spawn('pandoc', args);

reference.docx 是一次性生成的模板,可设置所选字体(例如中文使用 Microsoft JhengHei UI)、标题颜色和段落间距。通过 scripts/gen-reference-docx.mjs 生成——pandoc 会复制其中的样式。

注意:DOCX 对原始 HTML 的支持不完整。 <figure-grid> 块在 Word 中无法呈现为网格——会退化为纵向排列。图像请使用 Markdown ![alt](path) 语法,以确保两种格式都能保留图像。

编排模块结构

compose-ebook.mjs 会为每种章节类型导出一个函数:

export async function loadSources() { /* read COURSE + materials + assets */ }
export async function composeCover(meta) { /* big title, day list, meta card */ }
export async function composeOverview(meta) { /* TOC + course overview */ }
export async function composeChapter(day, assetIndex) { /* hero image + units */ }
export async function composeAppendixMaterials(materials) { /* full-text material appendix */ }
export async function composeAppendixSkills(skills) { /* QR-coded skill links */ }

每个函数都会返回一个 Markdown 字符串。build-ebook.mjs 会用 \n\n---\n\n 将它们串接起来。

资源路径策略

在编排后的 Markdown 中,图像使用相对于 Markdown 文件的路径(./assets/illustrations/foo.png)。Pandoc 的 --resource-path 可以解析这些路径;Playwright 的 file:// HTML 加载器也可以。不要使用绝对路径。

对于带资源回退机制的企业版,应在编排时解析实际文件位置并嵌入解析后的路径:

async function pickAsset(name, roots) {
  for (const root of roots) {
    const full = path.join(root, name);
    try { await fs.access(full); return path.relative(mdDir, full); }
    catch { continue; }
  }
  return null;  // caller decides whether to drop the image or fall back to placeholder
}

需要筛除的内容

网站上有些内容不应放入电子书:

  • 测验题目:泄露考题会破坏评估效果。示例工作坊会筛除整个测验章节。
  • 前测/后测部分:原因相同。
  • 仅限交互的功能:“点击复制”按钮在印刷品中毫无意义。请呈现原始提示词文本,而非按钮。
  • 动态插图:任何通过 canvas/JS 在客户端生成的内容都无法渲染。

请在编排函数中添加筛选条件;不要尝试先渲染再删除。

验证

生成后,进行验证:

// scripts/verify-ebook.mjs
// - PDF file exists and is > 100KB
// - First page (cover) has no page number
// - TOC pages have entries
// - Every image reference resolved (no broken-image placeholders)

对于 DOCX,请用 Word 打开一次并进行目视检查:目录已链接、字体已应用、没有缺失图片方块。

CLI 约定

示例工作坊的 build:ebook 脚本支持:

标志行为
(无标志)同时生成 PDF + DOCX
--md-only在合成 markdown 后停止(用于调试内容)
--no-docx跳过 DOCX(加快 PDF 迭代)
--no-pdf跳过 PDF(加快 DOCX 迭代)
--keep-html不删除中间 HTML(用于调试 CSS)
--output PATH自定义 PDF 输出路径

从第一天起就提供这些选项——你会多次迭代版式。

反模式

  • 在电子书构建器中重新创作内容——电子书是衍生内容。如果你发现自己在合成函数中添加新文案,这些文案应该放在 course-content-authoring 中。
  • 使用 Chrome CLI 生成 PDF——尽早改用 Playwright;你最终会需要页脚。
  • 硬编码图片路径——使用带有多个根目录的 pickAsset(),尤其是在支持企业版回退时。
  • 忘记 @page :first { margin: 0 }——封面页脚上会多出一个页码。
  • 网站每次更改都重新构建电子书——只在稳定的检查点构建;中间的 dist/ 文件夹很大。

交接

此技能完成后:

  • 已生成 dist/{course-name}.pdf,并可选择生成 .docx。
  • 验证脚本通过。
  • 如果面向客户,文件会放入交付包中。

告诉用户:“电子书已就绪。网站和电子书现在已同步;从现在起,以网站为准,仅在发布里程碑时重新构建电子书——不要直接编辑电子书,重新构建时会覆盖这些修改。”

评论

支持 Markdown