@code2rich/jpage 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +3 -1
- package/.github/workflows/release.yml +87 -0
- package/CLAUDE.md +3 -1
- package/README.md +26 -2
- package/bin/commands/ls.js +3 -1
- package/bin/commands/update.js +74 -0
- package/bin/jpage.js +7 -2
- package/docs/RELEASING.md +209 -0
- package/docs/skill-integration-design.md +384 -0
- package/eslint.config.mjs +2 -0
- package/lib/csp.js +8 -2
- package/lib/render.js +9 -2
- package/lib/templates.js +1 -1
- package/package.json +4 -4
- package/public/css/style.css +128 -1
- package/public/index.html +51 -3
- package/public/js/app.js +8 -6
- package/public/js/pages/content-templates.js +1 -1
- package/public/js/pages/home.js +218 -9
- package/public/js/pages/landing.js +1 -1
- package/public/js/pages/preview.js +1 -1
- package/public/js/utils.js +15 -7
- package/routes/skills.js +77 -3
- package/server.js +10 -3
- package/skills/jpage-presentation/INSTALL.md +50 -0
- package/skills/jpage-presentation/README.md +71 -0
- package/skills/jpage-presentation/SKILL.md +226 -0
- package/skills/jpage-presentation/assets/plugin/highlight/monokai.css +71 -0
- package/skills/jpage-presentation/assets/plugin/highlight/plugin.js +439 -0
- package/skills/jpage-presentation/assets/plugin/notes/notes.js +1 -0
- package/skills/jpage-presentation/assets/reveal-base.css +9 -0
- package/skills/jpage-presentation/assets/reveal.js +9 -0
- package/skills/jpage-presentation/assets/themes/academic.css +68 -0
- package/skills/jpage-presentation/assets/themes/business.css +64 -0
- package/skills/jpage-presentation/assets/themes/creative.css +81 -0
- package/skills/jpage-presentation/assets/themes/minimal.css +117 -0
- package/skills-registry.js +0 -6
- package/test/dispatch-bench.js +0 -3
- package/test/integration/cli.test.js +93 -0
- package/test/integration/skills.test.js +27 -5
- package/test/perf-harness.js +0 -9
- package/test/unit/fts.test.js +0 -1
- package/.claude/settings.local.json +0 -68
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { api, API_BASE } from '../api.js';
|
|
4
4
|
import { toast } from '../components/toast.js';
|
|
5
5
|
import { dialogModal } from '../components/dialog.js';
|
|
6
|
-
import { escapeHtml, formatSize, relativeTime
|
|
6
|
+
import { escapeHtml, formatSize, relativeTime } from '../utils.js';
|
|
7
7
|
import { state, navigate } from '../app.js';
|
|
8
8
|
import { closeTemplateSelect } from './home.js';
|
|
9
9
|
|
package/public/js/utils.js
CHANGED
|
@@ -41,15 +41,23 @@ function formatDate(iso) {
|
|
|
41
41
|
|
|
42
42
|
function esc(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }
|
|
43
43
|
|
|
44
|
-
function buildSkeletonCards(n) {
|
|
44
|
+
function buildSkeletonCards(n, viewMode) {
|
|
45
45
|
let html = '';
|
|
46
46
|
for (let i = 0; i < n; i++) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
if (viewMode === 'card') {
|
|
48
|
+
html += '<div class="skeleton-item skeleton-card" aria-hidden="true">'
|
|
49
|
+
+ '<div class="skeleton-card-thumb"></div>'
|
|
50
|
+
+ '<div class="skeleton-line skeleton-w60"></div>'
|
|
51
|
+
+ '<div class="skeleton-line skeleton-w40"></div>'
|
|
52
|
+
+ '</div>';
|
|
53
|
+
} else {
|
|
54
|
+
html += '<div class="skeleton-item" aria-hidden="true">'
|
|
55
|
+
+ '<div class="skeleton-icon"></div>'
|
|
56
|
+
+ '<div class="skeleton-lines">'
|
|
57
|
+
+ '<div class="skeleton-line skeleton-w60"></div>'
|
|
58
|
+
+ '<div class="skeleton-line skeleton-w40"></div>'
|
|
59
|
+
+ '</div></div>';
|
|
60
|
+
}
|
|
53
61
|
}
|
|
54
62
|
return html;
|
|
55
63
|
}
|
package/routes/skills.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Skills
|
|
2
|
-
// 挂载点:/api(内部路径 /skills、/skills/:name、/skills/:name/download、/mcp/config)
|
|
1
|
+
// Skills、MCP 配置、CLI 指南路由。从 server.js 提取,行为保持不变。
|
|
2
|
+
// 挂载点:/api(内部路径 /skills、/skills/:name、/skills/:name/download、/mcp/config、/cli/guide)
|
|
3
|
+
// 设计:CLI 与 MCP 是并列的两个客户端入口(REST / MCP),各有独立端点与 UI 入口,不互相嵌套。
|
|
3
4
|
|
|
4
5
|
const express = require('express');
|
|
5
6
|
const { dbAll } = require('../lib/db');
|
|
@@ -41,6 +42,63 @@ function buildServerConfig(url, token) {
|
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
// 生成 CLI 用法文档 Markdown。
|
|
46
|
+
// baseUrl 预填进示例(从 /mcp URL 去掉 /mcp 后缀得到),方便用户复制即用。
|
|
47
|
+
// token 用占位符 <YOUR_TOKEN>,不泄露真实 token。
|
|
48
|
+
function buildCliGuide(baseUrl) {
|
|
49
|
+
return `# jpage CLI
|
|
50
|
+
|
|
51
|
+
\`jpage\` 是即页的命令行工具,通过 REST API 上传/列出/管理文件。与 MCP 是**对称的两个入口**:
|
|
52
|
+
|
|
53
|
+
- **有 Bash 的场景**(Claude Code/ZCode 等 agent、脚本、CI、人工操作)→ 用 **CLI**:multipart 二进制流式上传,大文件/ZIP 不进 token 流,快且省
|
|
54
|
+
- **纯 MCP 客户端**(Claude Desktop 等无 Bash)→ 用 **MCP**
|
|
55
|
+
|
|
56
|
+
## 安装
|
|
57
|
+
|
|
58
|
+
\`\`\`bash
|
|
59
|
+
npm install -g @code2rich/jpage
|
|
60
|
+
jpage --help
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
## 配置 token
|
|
64
|
+
|
|
65
|
+
CLI 需要一个 token(\`jp_\` 用户 token 或全局 \`MCP_TOKEN\`)。优先级:
|
|
66
|
+
|
|
67
|
+
\`--token\` > \`JPAGE_TOKEN\` 环境变量 > \`.env\` 里的 \`MCP_TOKEN\`
|
|
68
|
+
|
|
69
|
+
在本系统的 **API 令牌** 页创建或复制你的 token。服务地址默认 \`${baseUrl}\`,可用 \`--base\` 覆盖。
|
|
70
|
+
|
|
71
|
+
## 命令
|
|
72
|
+
|
|
73
|
+
| 命令 | 说明 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| \`upload <路径> [--public] [--overwrite ID]\` | 上传(ZIP 自动判 bundle/batch) |
|
|
76
|
+
| \`ls [--page --limit --kw --cat --tag]\` | 列出文件 |
|
|
77
|
+
| \`cat <id>\` | 输出文件内容 |
|
|
78
|
+
| \`url <id>\` | 打印 /s/:key 预览链接 |
|
|
79
|
+
| \`mv <id> <新名> [--public|--private]\` | 改名 / 公开性 |
|
|
80
|
+
| \`rm <id> [--yes]\` | 删除 |
|
|
81
|
+
| \`star <id>\` / \`unstar <id>\` | 收藏 / 取消收藏 |
|
|
82
|
+
| \`tags <id> [add|set|clear] [名,名,...]\` | 标签(追加/替换/清空) |
|
|
83
|
+
| \`skills ls | get <名> | download <名>\` | Skill 包 |
|
|
84
|
+
| \`whoami\` | 校验 token 是否有效 |
|
|
85
|
+
|
|
86
|
+
## 示例
|
|
87
|
+
|
|
88
|
+
\`\`\`bash
|
|
89
|
+
jpage upload ./report.html --public --base ${baseUrl}
|
|
90
|
+
jpage ls --kw 季度
|
|
91
|
+
jpage cat 8
|
|
92
|
+
jpage tags 8 add Q3,财报
|
|
93
|
+
jpage url 8
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
> 首次使用需先设 token:\`export JPAGE_TOKEN=<YOUR_TOKEN>\`,或在命令后加 \`--token <YOUR_TOKEN>\`。
|
|
97
|
+
>
|
|
98
|
+
> 完整说明:\`jpage --help\`
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
|
|
44
102
|
router.get('/mcp/config', requireAuth, async (req, res) => {
|
|
45
103
|
const enabled = !!process.env.MCP_TOKEN || true; // 现在总是可以用用户级 Token
|
|
46
104
|
const host = req.headers.host || `localhost:${process.env.PORT || 8858}`;
|
|
@@ -55,7 +113,8 @@ router.get('/mcp/config', requireAuth, async (req, res) => {
|
|
|
55
113
|
|
|
56
114
|
const globalToken = process.env.MCP_TOKEN && req.userRole === 'admin' ? process.env.MCP_TOKEN : null;
|
|
57
115
|
|
|
58
|
-
//
|
|
116
|
+
// 所有 MCP 客户端共用同一配置对象;差异仅在目标文件路径/说明文字。
|
|
117
|
+
// CLI 不属于 MCP 客户端(它是 REST 命令行入口),走独立的 /api/cli/guide 端点,不混在此处。
|
|
59
118
|
const config = buildServerConfig(url, globalToken);
|
|
60
119
|
const configs = [
|
|
61
120
|
{ id: 'claude-code', label: 'Claude Code', path: '.mcp.json(项目根)或 ~/.claude.json', config },
|
|
@@ -75,6 +134,21 @@ router.get('/mcp/config', requireAuth, async (req, res) => {
|
|
|
75
134
|
});
|
|
76
135
|
});
|
|
77
136
|
|
|
137
|
+
// CLI 用法指南。CLI 与 MCP 是并列的两个客户端入口(都架在同一套 REST API 上),
|
|
138
|
+
// 故独立成端点,供「CLI 工具」菜单/弹窗取用,不挂在 MCP 配置之下。
|
|
139
|
+
router.get('/cli/guide', requireAuth, (req, res) => {
|
|
140
|
+
const host = req.headers.host || `localhost:${process.env.PORT || 8858}`;
|
|
141
|
+
const protocol = req.protocol || 'http';
|
|
142
|
+
const baseUrl = `${protocol}://${host}`;
|
|
143
|
+
const guideText = buildCliGuide(baseUrl);
|
|
144
|
+
res.json({
|
|
145
|
+
enabled: true,
|
|
146
|
+
baseUrl,
|
|
147
|
+
guideText,
|
|
148
|
+
guideHtml: marked.parse(guideText, { gfm: true, breaks: false, async: false })
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
78
152
|
router.get('/skills/:name/download', requireAuth, (req, res) => {
|
|
79
153
|
const archive = createZipStream(req.params.name);
|
|
80
154
|
if (!archive) return res.status(404).json({ error: 'Skill 不存在' });
|
package/server.js
CHANGED
|
@@ -67,7 +67,9 @@ const app = express();
|
|
|
67
67
|
app.set('trust proxy', 1);
|
|
68
68
|
|
|
69
69
|
// helmet:关闭内置 CSP(由下方手写中间件 + render.js 分级下发),
|
|
70
|
-
//
|
|
70
|
+
// frameguard 默认 deny(全局 X-Frame-Options: DENY),但渲染端点需被同源 iframe 嵌入(文件列表卡片缩略图),
|
|
71
|
+
// 故在下方 CSP 中间件里对渲染端点移除该头,改由 lib/render.js 显式下发 X-Frame-Options: SAMEORIGIN + CSP frame-ancestors 'self',
|
|
72
|
+
// 只允许同源嵌入、外站仍被拒。
|
|
71
73
|
// crossOriginEmbedderPolicy 关闭:渲染端点会加载用户内容(可能含未带 CORP 的子资源)。
|
|
72
74
|
app.use(helmet({
|
|
73
75
|
contentSecurityPolicy: false,
|
|
@@ -77,9 +79,14 @@ app.use(helmet({
|
|
|
77
79
|
|
|
78
80
|
// CSP 中间件:管理界面下发严格 APP_CSP;渲染端点(render/asset/短链)跳过,
|
|
79
81
|
// 由 lib/render.js 的 renderFile 按内容类型分级下发 Markdown/HTML 策略。
|
|
82
|
+
// 渲染端点还需被同源 iframe 嵌入(文件列表卡片缩略图),故移除 helmet 全局下发的
|
|
83
|
+
// X-Frame-Options: DENY,改由 render.js 下发 SAMEORIGIN(仅同源可嵌入)。
|
|
80
84
|
const { APP_CSP, isRenderPath } = require('./lib/csp');
|
|
81
85
|
app.use((req, res, next) => {
|
|
82
|
-
if (isRenderPath(req.path))
|
|
86
|
+
if (isRenderPath(req.path)) {
|
|
87
|
+
res.removeHeader('X-Frame-Options'); // 由 render.js 按需下发 SAMEORIGIN
|
|
88
|
+
return next();
|
|
89
|
+
}
|
|
83
90
|
res.setHeader('Content-Security-Policy', APP_CSP);
|
|
84
91
|
next();
|
|
85
92
|
});
|
|
@@ -244,7 +251,7 @@ app.get('*', (req, res) => {
|
|
|
244
251
|
});
|
|
245
252
|
|
|
246
253
|
// --- 全局错误处理 ---
|
|
247
|
-
app.use((err, req, res,
|
|
254
|
+
app.use((err, req, res, _next) => {
|
|
248
255
|
logger.error({ type: 'app', message: err.message, stack: err.stack });
|
|
249
256
|
if (err instanceof multer.MulterError) {
|
|
250
257
|
if (err.code === 'LIMIT_FILE_SIZE') return res.status(400).json({ error: '文件大小超过50MB限制' });
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# 安装 jpage-presentation Skill
|
|
2
|
+
|
|
3
|
+
本 Skill 适用于支持 **SKILL.md 开放标准** 的 AI 客户端(Claude Code、Claude Desktop、OpenAI Codex CLI、Cursor 等)。
|
|
4
|
+
|
|
5
|
+
## 前置条件
|
|
6
|
+
|
|
7
|
+
- AI 客户端已配置即页的 MCP 端点(`/mcp`),且 `upload_file` 工具可用
|
|
8
|
+
- 若要用 curl multipart 上传(推荐):运行环境有 Bash 能力
|
|
9
|
+
|
|
10
|
+
## 方式一:从即页 Web UI 下载 ZIP(最简单)
|
|
11
|
+
|
|
12
|
+
1. 打开即页首页,找到 Skills 区块
|
|
13
|
+
2. 点击 `jpage-presentation` → 下载 ZIP
|
|
14
|
+
3. 解压到客户端的 skills 目录(见下方各客户端路径)
|
|
15
|
+
|
|
16
|
+
## 方式二:从仓库直接复制
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cp -r /path/to/jpage/skills/jpage-presentation ~/.claude/skills/
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 各客户端 skills 目录
|
|
23
|
+
|
|
24
|
+
| 客户端 | 路径 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| Claude Code | `~/.claude/skills/` |
|
|
27
|
+
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/skills/` |
|
|
28
|
+
| Claude Desktop (Windows) | `%APPDATA%\Claude\skills\` |
|
|
29
|
+
|
|
30
|
+
## 验证安装
|
|
31
|
+
|
|
32
|
+
1. 重启 AI 客户端(让它重新扫描 skills 目录)
|
|
33
|
+
2. 对 AI 说:「做一个关于 X 的 5 页 PPT,商务风格」
|
|
34
|
+
3. AI 应触发本 Skill,生成 reveal.js 幻灯片并上传到即页
|
|
35
|
+
|
|
36
|
+
## assets 目录说明
|
|
37
|
+
|
|
38
|
+
本 Skill 的 `assets/` 目录已随包下发 reveal.js 5.x 引擎、基础 CSS、四套主题、notes/highlight 插件骨架。生成幻灯片时,AI 会把这些文件复制到目标 `deck/assets/`,**不需要额外下载**(除非要用代码高亮,见 SKILL.md「highlight.js 按需获取」)。
|
|
39
|
+
|
|
40
|
+
## 更新 reveal.js
|
|
41
|
+
|
|
42
|
+
`assets/reveal.js` 和 `assets/reveal-base.css` 是 reveal.js 5.2.1 的官方产物。升级时:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm pack reveal.js@5
|
|
46
|
+
tar -xzf reveal.js-5.*.tgz --strip-components=1 -C assets package/dist/reveal.js package/dist/reveal.css
|
|
47
|
+
mv assets/reveal.css assets/reveal-base.css
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
插件同理,从 `package/plugin/` 复制对应文件。
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# jpage-presentation
|
|
2
|
+
|
|
3
|
+
用自然语言生成基于 [reveal.js](https://revealjs.com/) 的自包含幻灯片,上传到即页后返回分享链接。
|
|
4
|
+
|
|
5
|
+
支持四套主题:
|
|
6
|
+
|
|
7
|
+
| 主题 | 文件 | 主色 | 适用场景 |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| 商务 | `assets/themes/business.css` | 深蓝 + 白底 | 季度汇报、商业提案、年终总结 |
|
|
10
|
+
| 学术 | `assets/themes/academic.css` | 深灰 + 米白、衬线标题 | 论文答辩、学术分享、研究报告 |
|
|
11
|
+
| 创意 | `assets/themes/creative.css` | 高饱和深底渐变 | 产品发布、创意提案、活动 keynote |
|
|
12
|
+
| 极简 | `assets/themes/minimal.css` | 黑白 + 蓝色强调 | 极简主义、keynote/苹果风 |
|
|
13
|
+
|
|
14
|
+
## 为什么用 Bundle 模式
|
|
15
|
+
|
|
16
|
+
reveal.js 引擎 ~112KB。若内联进每个幻灯片 HTML,10 个 PPT 就要重复 10 份引擎代码。即页的 **Bundle 机制**(ZIP 解压成目录 + `<base>` 注入)让 reveal.js 作为公共资源放一次,多张幻灯片复用,且**离线可预览**(不依赖 CDN)。
|
|
17
|
+
|
|
18
|
+
## 工作流
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
用户:"做一个 Q3 汇报 PPT,商务风格"
|
|
22
|
+
↓
|
|
23
|
+
1. 选商务主题
|
|
24
|
+
2. 规划结构(封面 / 内容 / 分隔 / 总结)
|
|
25
|
+
3. 生成 deck/(index.html + assets/,assets 含 reveal.js + 主题 CSS)
|
|
26
|
+
4. 打 ZIP 上传到即页(bundle 自动识别)
|
|
27
|
+
5. 返回 /s/<share_key>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 目录结构
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
jpage-presentation/
|
|
34
|
+
├── SKILL.md # AI 工作流指令(核心)
|
|
35
|
+
├── README.md # 本文件
|
|
36
|
+
├── INSTALL.md # 安装说明
|
|
37
|
+
└── assets/ # 随包下发的 reveal.js 资源
|
|
38
|
+
├── reveal.js # reveal.js 5.x 引擎(112KB)
|
|
39
|
+
├── reveal-base.css # reveal.js 基础布局(54KB,必须)
|
|
40
|
+
├── themes/
|
|
41
|
+
│ ├── business.css # 商务主题(CSS 变量驱动)
|
|
42
|
+
│ ├── academic.css # 学术主题
|
|
43
|
+
│ ├── creative.css # 创意主题
|
|
44
|
+
│ └── minimal.css # 极简主题
|
|
45
|
+
└── plugin/
|
|
46
|
+
├── highlight/ # 代码高亮(monokai 主题 + 加载器;highlight.js 按需获取)
|
|
47
|
+
└── notes/ # 演讲者备注
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 关键技术决策
|
|
51
|
+
|
|
52
|
+
- **`embedded: true`**:reveal.js 必须配置此项,适配即页预览的 iframe 沙箱(父页面会抢方向键/空格,`embedded` 让 reveal 依赖容器点击聚焦)。
|
|
53
|
+
- **禁用 CDN**:所有资源打进 `assets/`,保证内网/断网可预览。
|
|
54
|
+
- **两个 CSS**:`reveal-base.css`(基础布局,必须)+ `theme.css`(四选一,覆盖 CSS 变量)。顺序:base 在前,theme 在后。
|
|
55
|
+
- **上传通道**:有 Bash 时走 curl multipart(二进制流式,快);纯 MCP 走 `upload_file` 的 base64(体积大时慢且费 token)。
|
|
56
|
+
|
|
57
|
+
## 验证状态
|
|
58
|
+
|
|
59
|
+
| 验证项 | 结果 |
|
|
60
|
+
|---|---|
|
|
61
|
+
| Bundle 分类(flat 结构)| ✅ 通过(根级 index.html + assets/ 判为 bundle)|
|
|
62
|
+
| Bundle 分类(wrapped 结构)| ✅ 通过(套顶层目录也能判 bundle)|
|
|
63
|
+
| `<base>` 注入 + 资源 200 | ✅ 通过(reveal.js / theme.css / reveal-base.css 全 200)|
|
|
64
|
+
| wrapped 包资源 404 bug | ✅ 已修复(`<base>` 指向 entry 所在目录而非 bundle 根)|
|
|
65
|
+
| 测试套件回归 | ✅ 173/173 通过 |
|
|
66
|
+
| iframe 内 embedded 翻页 | ⏳ 需浏览器实测(设计上 `embedded:true` 规避抢键)|
|
|
67
|
+
|
|
68
|
+
## 致谢
|
|
69
|
+
|
|
70
|
+
- [reveal.js](https://revealjs.com/) by Hakim El Hattab(MIT 许可)—— 幻灯片引擎
|
|
71
|
+
- [ryanbbrown/revealjs-skill](https://github.com/ryanbbrown/revealjs-skill) —— 脚手架思路与结构语法的灵感来源
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jpage-presentation
|
|
3
|
+
description: 当用户要"生成 PPT""做幻灯片""演示文稿""做一个 deck""答辩 slides"时,生成基于 reveal.js 的自包含幻灯片网站包(Bundle)并上传到即页,返回分享链接。支持商务/学术/创意/极简四套主题。区别于普通 HTML:必须用 Bundle 模式(ZIP 含 index.html + assets/),不要单文件内联 reveal.js。
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: jpage
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 核心规则
|
|
9
|
+
|
|
10
|
+
用户要幻灯片 / PPT / 演示文稿时,按下面的 **Bundle 工作流**生成并上传,不要生成单文件 HTML。
|
|
11
|
+
|
|
12
|
+
# 触发场景
|
|
13
|
+
|
|
14
|
+
- 用户说"生成 PPT""做幻灯片""演示文稿""做一个 deck"
|
|
15
|
+
- 用户说"Q3 汇报""产品发布""答辩 slides""培训课件"
|
|
16
|
+
- 用户给一份文档/笔记,要求"转成幻灯片""做成 PPT"
|
|
17
|
+
|
|
18
|
+
# Bundle 工作流(必须遵守)
|
|
19
|
+
|
|
20
|
+
reveal.js 引擎 ~85KB,单文件内联会让每个 PPT 膨胀。即页有 **Bundle 机制**(ZIP 解压成目录,资源共用,离线可预览),必须走这条路。
|
|
21
|
+
|
|
22
|
+
## 1. 规划结构
|
|
23
|
+
|
|
24
|
+
先规划幻灯片结构,再动手生成。常见结构:
|
|
25
|
+
|
|
26
|
+
- **简单式**:封面 → N 张内容 → 总结
|
|
27
|
+
- **章节式**:封面 → 章节分隔页 → 内容页(可垂直堆叠)→ 下一章节分隔 → 内容 → 总结
|
|
28
|
+
|
|
29
|
+
结构语法(可选,用于脚手架脚本):`1` = 单张水平页,`N` = N 张垂直堆叠,`d` = 居中大字的分隔页。
|
|
30
|
+
例:`1,d,3,d,2,d,1` = 封面 / 分隔 / 3 页内容 / 分隔 / 2 页内容 / 分隔 / 总结。
|
|
31
|
+
|
|
32
|
+
## 2. 选主题
|
|
33
|
+
|
|
34
|
+
根据用户语气选主题(用户没指定时默认**商务**):
|
|
35
|
+
|
|
36
|
+
| 用户说 | 主题 | 主色 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| 商务 / 汇报 / 正式 / 提案 / 季度 / 年终 | 商务 (business) | 深蓝 #0a4d8c + 白底 |
|
|
39
|
+
| 学术 / 论文 / 答辩 / 研究 | 学术 (academic) | 深灰 + 米白,衬线标题 |
|
|
40
|
+
| 创意 / 产品 / 发布 / 活泼 / 设计 | 创意 (creative) | 高饱和渐变 |
|
|
41
|
+
| 极简 / 简约 / keynote 风 / 苹果风 | 极简 (minimal) | 黑白 + 一个强调色 |
|
|
42
|
+
|
|
43
|
+
## 3. 生成多文件网站包
|
|
44
|
+
|
|
45
|
+
写到磁盘,目录结构推荐如下(**根级直接是 index.html + assets/**,即 flat 结构):
|
|
46
|
+
|
|
47
|
+
> 💡 实测:套顶层目录(如 `deck/index.html`)也能正确识别为 bundle 并渲染,但 flat 结构更简洁,推荐。
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
deck/
|
|
51
|
+
├── index.html # reveal.js 容器,<div class="reveal"><div class="slides">...</div></div>
|
|
52
|
+
├── assets/
|
|
53
|
+
│ ├── reveal.css # 从本 Skill 的 assets/themes/<主题>.css 取(已下发)
|
|
54
|
+
│ ├── reveal.js # reveal.js 引擎(见下文"获取 reveal.js")
|
|
55
|
+
│ └── plugin/ # markdown / highlight / notes 插件(按需,见下文)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### index.html 必须满足
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<!DOCTYPE html>
|
|
62
|
+
<html lang="zh-CN">
|
|
63
|
+
<head>
|
|
64
|
+
<meta charset="UTF-8">
|
|
65
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
66
|
+
<title>幻灯片标题</title>
|
|
67
|
+
<link rel="stylesheet" href="assets/reveal-base.css"> <!-- reveal.js 基础布局(必须) -->
|
|
68
|
+
<link rel="stylesheet" href="assets/theme.css"> <!-- 选定的主题(覆盖 CSS 变量) -->
|
|
69
|
+
<link rel="stylesheet" href="assets/plugin/highlight/monokai.css"> <!-- 代码高亮主题,按需 -->
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div class="reveal">
|
|
73
|
+
<div class="slides">
|
|
74
|
+
<section><h1>标题</h1><p>副标题</p></section>
|
|
75
|
+
<section>水平页</section>
|
|
76
|
+
<section>
|
|
77
|
+
<section>垂直堆叠页 1</section>
|
|
78
|
+
<section>垂直堆叠页 2</section>
|
|
79
|
+
</section>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<script src="assets/reveal.js"></script>
|
|
83
|
+
<script src="assets/plugin/highlight/highlight.js"></script>
|
|
84
|
+
<script>
|
|
85
|
+
Reveal.initialize({
|
|
86
|
+
embedded: true, // ★ 必须 true:iframe 内兼容,依赖容器聚焦而非全局键盘
|
|
87
|
+
hash: true,
|
|
88
|
+
controls: true,
|
|
89
|
+
progress: true,
|
|
90
|
+
slideNumber: true,
|
|
91
|
+
transition: 'slide',
|
|
92
|
+
plugins: [RevealHighlight]
|
|
93
|
+
});
|
|
94
|
+
</script>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**关键约束**:
|
|
100
|
+
- ⚠️ `Reveal.initialize` 必须设 **`embedded: true`**——即页预览在 iframe 里,父页面会抢方向键/空格,`embedded:true` 让 reveal 依赖容器点击聚焦,避免抢键冲突。
|
|
101
|
+
- ⚠️ **不要引用任何 CDN**(jsdelivr/unpkg/cdnjs 都不行)。即页核心卖点是离线可预览,走 CDN 会让内网/断网用户看到空白。所有 reveal.js 资源必须打进 `assets/`。
|
|
102
|
+
- ⚠️ 每页 `<section>` 内容精简。reveal.js **不自动滚动**,内容溢出会被裁切。一张幻灯片讲一个要点。
|
|
103
|
+
- 中文字体用系统栈:`-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif`。
|
|
104
|
+
|
|
105
|
+
## 4. 获取 reveal.js 资源
|
|
106
|
+
|
|
107
|
+
reveal.js 引擎 + 基础 CSS + 4 套主题 + notes/highlight 插件骨架**已随本 Skill 包下发**(`assets/` 目录,~312KB)。生成幻灯片时直接复制这些文件到目标 `deck/assets/`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 假设本 Skill 已安装到 ~/.claude/skills/jpage-presentation/
|
|
111
|
+
SKILL=~/.claude/skills/jpage-presentation
|
|
112
|
+
|
|
113
|
+
mkdir -p deck/assets/plugin/notes deck/assets/plugin/highlight
|
|
114
|
+
cp "$SKILL/assets/reveal.js" deck/assets/
|
|
115
|
+
cp "$SKILL/assets/reveal-base.css" deck/assets/ # reveal.js 基础布局(必须)
|
|
116
|
+
cp "$SKILL/assets/themes/business.css" deck/assets/theme.css # 选定主题,见第 2 步
|
|
117
|
+
cp "$SKILL/assets/plugin/notes/notes.js" deck/assets/plugin/notes/ # 演讲者备注,可选
|
|
118
|
+
cp "$SKILL/assets/plugin/highlight/plugin.js" deck/assets/plugin/highlight/ # 代码高亮加载器
|
|
119
|
+
cp "$SKILL/assets/plugin/highlight/monokai.css" deck/assets/plugin/highlight/ # 高亮主题
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**必须引入的两个 CSS**:`reveal-base.css`(reveal.js 基础布局)+ `theme.css`(四套主题之一,覆盖 CSS 变量)。顺序:base 在前,theme 在后。
|
|
123
|
+
|
|
124
|
+
### highlight.js 按需获取(可选)
|
|
125
|
+
|
|
126
|
+
本 Skill 默认**不含** highlight.js 全量文件(940KB,太重)。若幻灯片需要代码高亮:
|
|
127
|
+
```bash
|
|
128
|
+
npm pack highlight.js@11 && tar -xzf highlight.js-*.tgz --strip-components=1 -C deck/assets/plugin/highlight package/es/highlight.min.js package/styles/monokai.css
|
|
129
|
+
```
|
|
130
|
+
不需要代码高亮时,不要引入,省体积。
|
|
131
|
+
|
|
132
|
+
### 找不到本 Skill 包时
|
|
133
|
+
|
|
134
|
+
若运行环境没有本 Skill 的 `assets/`(如只贴了 SKILL.md),从 npm 拿稳定版:
|
|
135
|
+
```bash
|
|
136
|
+
npm pack reveal.js@5
|
|
137
|
+
tar -xzf reveal.js-5.*.tgz --strip-components=1 -C deck/assets package/dist/reveal.js package/dist/reveal.css package/plugin/
|
|
138
|
+
mv deck/assets/reveal.css deck/assets/reveal-base.css
|
|
139
|
+
# 主题则用本 SKILL.md 里第 5 节的 CSS 变量自定义,或参照 themes/ 目录手写
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 5. 上传(Bundle 模式)
|
|
143
|
+
|
|
144
|
+
打包 ZIP 并上传。推荐 **flat 结构**(ZIP 根级直接是 index.html + assets/)。
|
|
145
|
+
|
|
146
|
+
### 有 Bash(Claude Code)→ curl multipart(推荐,快)
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
TOKEN=$(grep -E '^MCP_TOKEN=' .env 2>/dev/null | cut -d= -f2-)
|
|
150
|
+
[ -z "$TOKEN" ] && TOKEN=$(grep -oE 'Bearer [A-Za-z0-9_]+' .mcp.json 2>/dev/null | head -1 | awk '{print $2}')
|
|
151
|
+
BASE="${JPAGE_BASE:-http://localhost:8858}"
|
|
152
|
+
|
|
153
|
+
# 打 ZIP:优先系统 zip 命令(flat 结构,根级直接 index.html)
|
|
154
|
+
if command -v zip >/dev/null 2>&1; then
|
|
155
|
+
( cd deck && zip -rq ../deck.zip index.html assets/ )
|
|
156
|
+
else
|
|
157
|
+
# 无 zip 命令时用 Node + JSZip(即页已依赖 jszip)
|
|
158
|
+
node -e '
|
|
159
|
+
const JSZip=require("jszip"),fs=require("fs"),path=require("path");
|
|
160
|
+
(async()=>{
|
|
161
|
+
const z=new JSZip();
|
|
162
|
+
(function add(root,base=""){for(const n of fs.readdirSync(root)){const a=path.join(root,n),r=base?base+"/"+n:n;
|
|
163
|
+
if(fs.statSync(a).isDirectory())add(a,r);else z.file(r,fs.readFileSync(a));}})(process.cwd()+"/deck");
|
|
164
|
+
fs.writeFileSync("deck.zip",await z.generateAsync({type:"nodebuffer"}));
|
|
165
|
+
})();
|
|
166
|
+
'
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
curl -sS -X POST "$BASE/api/files/upload" \
|
|
170
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
171
|
+
-F "file=@deck.zip" \
|
|
172
|
+
-F "isPublic=true"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
服务端自动判定为 bundle(根级有 index.html + assets 子目录),返回 `{id, share_key, ...}`。
|
|
176
|
+
(套了顶层目录的 ZIP 也能识别为 bundle,但 flat 更简洁。)
|
|
177
|
+
|
|
178
|
+
### 纯 MCP(Claude Desktop 无 Bash)→ upload_file base64
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
upload_file(
|
|
182
|
+
name="ai-trends-deck.zip",
|
|
183
|
+
content="<ZIP 的 base64>", # base64(文件二进制)
|
|
184
|
+
isPublic=True
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
⚠️ 体积提示:reveal.js ~85KB 经 base64 约 113KB,作为 tool 参数流经模型 token 流,比 curl multipart 慢且费 token。有 Bash 就别走这条。
|
|
189
|
+
|
|
190
|
+
## 6. 返回并提示翻页方式
|
|
191
|
+
|
|
192
|
+
上传成功后向用户展示 `/s/<share_key>` 链接,并附一句提示:
|
|
193
|
+
|
|
194
|
+
> 幻灯片打开后,**点击幻灯片区域聚焦**,再用 ← → 翻页(或点右下角控件)。若键盘不响应,点预览页右上角"新窗口打开"按钮全屏查看。
|
|
195
|
+
|
|
196
|
+
# 内容规范
|
|
197
|
+
|
|
198
|
+
- **封面页**:大标题 + 副标题 + 作者/日期。用 `class="fit-text"` 或 `<h1>` 默认样式。
|
|
199
|
+
- **分隔页**:居中大字,标识新章节。`<section><h2 class="r-fit-text">章节名</h2></section>`。
|
|
200
|
+
- **内容页**:一个要点 + 3-5 条要点列表或一张图。不要堆字。
|
|
201
|
+
- **代码页**:用 `<pre><code class="language-js">...</code></pre>`,配 highlight 插件。
|
|
202
|
+
- **图表**:用 Mermaid(`<pre class="mermaid">...</pre>` 需引入 mermaid 插件)或内联 SVG。避免引入 Chart.js(增加体积)。
|
|
203
|
+
- **结尾页**:感谢 + 联系方式 / Q&A。
|
|
204
|
+
|
|
205
|
+
# 风格学习(可选)
|
|
206
|
+
|
|
207
|
+
若用户说"参照某风格",先 `list_content_templates(scene="presentation")` 取样例,学习其:
|
|
208
|
+
1. 配色方案 → 映射到四套主题之一或微调 CSS 变量
|
|
209
|
+
2. 版式(标题大小、留白) → 调 `--r-heading-font-size` 等
|
|
210
|
+
3. 装饰元素(分隔线、页码样式)
|
|
211
|
+
|
|
212
|
+
**学风格,不抄内容**。
|
|
213
|
+
|
|
214
|
+
# 常见坑
|
|
215
|
+
|
|
216
|
+
| 现象 | 原因 | 解决 |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| 上传后被当成多个独立文件(batch)而非一个幻灯片包 | ZIP 里没有 index.html,或有多个并列 HTML 且无共享资源目录 | 确保根级有 index.html + assets/ 子目录;只一个 HTML 时也会判 bundle |
|
|
219
|
+
| 幻灯片打开是空白 | 引用了 CDN 的 reveal.js | 所有资源必须本地 `assets/`,禁止 CDN URL |
|
|
220
|
+
| 翻页键不响应 | iframe 父页面抢键 | 已用 `embedded:true` 规避;提示用户先点击聚焦,或用新窗口打开 |
|
|
221
|
+
| 文字被裁切 | 一页内容太多 | 拆页,每页一个要点 |
|
|
222
|
+
| 主题样式没生效 | 只引入了 theme.css 没引入 reveal-base.css,或顺序反了 | 必须 `<link href="assets/reveal-base.css">` 在前,`<link href="assets/theme.css">` 在后,两者都在 reveal.js 之前 |
|
|
223
|
+
|
|
224
|
+
# 复用
|
|
225
|
+
|
|
226
|
+
上传环节**统一走 `jpage-upload` 的 `upload_file` 工具**,本 Skill 不另造上传逻辑。
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Monokai style - ported by Luigi Maselli - http://grigio.org
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.hljs {
|
|
6
|
+
display: block;
|
|
7
|
+
overflow-x: auto;
|
|
8
|
+
padding: 0.5em;
|
|
9
|
+
background: #272822;
|
|
10
|
+
color: #ddd;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.hljs-tag,
|
|
14
|
+
.hljs-keyword,
|
|
15
|
+
.hljs-selector-tag,
|
|
16
|
+
.hljs-literal,
|
|
17
|
+
.hljs-strong,
|
|
18
|
+
.hljs-name {
|
|
19
|
+
color: #f92672;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.hljs-code {
|
|
23
|
+
color: #66d9ef;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.hljs-class .hljs-title {
|
|
27
|
+
color: white;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.hljs-attribute,
|
|
31
|
+
.hljs-symbol,
|
|
32
|
+
.hljs-regexp,
|
|
33
|
+
.hljs-link {
|
|
34
|
+
color: #bf79db;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.hljs-string,
|
|
38
|
+
.hljs-bullet,
|
|
39
|
+
.hljs-subst,
|
|
40
|
+
.hljs-title,
|
|
41
|
+
.hljs-section,
|
|
42
|
+
.hljs-emphasis,
|
|
43
|
+
.hljs-type,
|
|
44
|
+
.hljs-built_in,
|
|
45
|
+
.hljs-builtin-name,
|
|
46
|
+
.hljs-selector-attr,
|
|
47
|
+
.hljs-selector-pseudo,
|
|
48
|
+
.hljs-addition,
|
|
49
|
+
.hljs-variable,
|
|
50
|
+
.hljs-template-tag,
|
|
51
|
+
.hljs-template-variable {
|
|
52
|
+
color: #a6e22e;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.hljs-comment,
|
|
56
|
+
.hljs-quote,
|
|
57
|
+
.hljs-deletion,
|
|
58
|
+
.hljs-meta {
|
|
59
|
+
color: #75715e;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.hljs-keyword,
|
|
63
|
+
.hljs-selector-tag,
|
|
64
|
+
.hljs-literal,
|
|
65
|
+
.hljs-doctag,
|
|
66
|
+
.hljs-title,
|
|
67
|
+
.hljs-section,
|
|
68
|
+
.hljs-type,
|
|
69
|
+
.hljs-selector-id {
|
|
70
|
+
font-weight: bold;
|
|
71
|
+
}
|