skill-base 2.0.3 → 2.0.6

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.
Files changed (46) hide show
  1. package/README.md +189 -85
  2. package/bin/skill-base.js +33 -7
  3. package/package.json +3 -1
  4. package/src/cappy.js +416 -0
  5. package/src/database.js +11 -0
  6. package/src/index.js +87 -24
  7. package/src/middleware/auth.js +96 -32
  8. package/src/routes/auth.js +1 -1
  9. package/src/routes/skills.js +10 -5
  10. package/src/utils/zip.js +15 -4
  11. package/static/android-chrome-192x192.png +0 -0
  12. package/static/android-chrome-512x512.png +0 -0
  13. package/static/apple-touch-icon.png +0 -0
  14. package/static/assets/index-BgwubB87.css +1 -0
  15. package/static/assets/index-DBHCo8Mz.js +230 -0
  16. package/static/favicon-16x16.png +0 -0
  17. package/static/favicon-32x32.png +0 -0
  18. package/static/favicon.ico +0 -0
  19. package/static/favicon.svg +14 -0
  20. package/static/index.html +18 -248
  21. package/static/site.webmanifest +1 -0
  22. package/static/admin/users.html +0 -593
  23. package/static/cli-code.html +0 -203
  24. package/static/css/.gitkeep +0 -0
  25. package/static/css/style.css +0 -1567
  26. package/static/diff.html +0 -466
  27. package/static/file.html +0 -443
  28. package/static/js/.gitkeep +0 -0
  29. package/static/js/admin/users.js +0 -346
  30. package/static/js/app.js +0 -508
  31. package/static/js/auth.js +0 -151
  32. package/static/js/cli-code.js +0 -184
  33. package/static/js/collaborators.js +0 -283
  34. package/static/js/diff.js +0 -540
  35. package/static/js/file.js +0 -619
  36. package/static/js/i18n.js +0 -739
  37. package/static/js/index.js +0 -168
  38. package/static/js/publish.js +0 -718
  39. package/static/js/settings.js +0 -124
  40. package/static/js/setup.js +0 -157
  41. package/static/js/skill.js +0 -808
  42. package/static/login.html +0 -82
  43. package/static/publish.html +0 -459
  44. package/static/settings.html +0 -163
  45. package/static/setup.html +0 -101
  46. package/static/skill.html +0 -851
package/static/js/diff.js DELETED
@@ -1,540 +0,0 @@
1
- /**
2
- * Skill Base - Diff 对比页逻辑
3
- */
4
-
5
- // 全局状态
6
- let skillId = null;
7
- let currentSkill = null;
8
- let versions = [];
9
- let currentVersionA = null;
10
- let currentVersionB = null;
11
- let currentFilePath = null;
12
- let zipA = null;
13
- let zipB = null;
14
- let outputFormat = 'side-by-side'; // 默认左右分栏
15
- let changedFiles = [];
16
-
17
- // 二进制文件扩展名
18
- const BINARY_EXTS = new Set([
19
- '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.webp',
20
- '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
21
- '.zip', '.tar', '.gz', '.rar', '.7z',
22
- '.exe', '.dll', '.so', '.dylib',
23
- '.mp3', '.mp4', '.wav', '.avi', '.mov',
24
- '.ttf', '.otf', '.woff', '.woff2',
25
- ]);
26
-
27
- // 页面加载时初始化
28
- document.addEventListener('DOMContentLoaded', async () => {
29
- const params = new URLSearchParams(window.location.search);
30
- skillId = params.get('id');
31
- currentFilePath = params.get('path') || null;
32
- currentVersionA = params.get('version_a') || null;
33
- currentVersionB = params.get('version_b') || null;
34
-
35
- if (!skillId) {
36
- showToast(t('diff.missingId'), 'error');
37
- setTimeout(() => window.location.href = '/', 1500);
38
- return;
39
- }
40
-
41
- // 1. 检查登录状态
42
- const user = await checkAuth();
43
- if (!user) return;
44
-
45
- // 2. 渲染导航栏
46
- renderNavbar(user);
47
-
48
- // 3. 加载 Skill 信息
49
- await loadSkillInfo();
50
-
51
- // 4. 加载版本列表填充下拉框
52
- await loadVersions();
53
-
54
- // 5. 如果有 version_a 和 version_b 参数,自动执行 diff
55
- if (currentVersionA && currentVersionB) {
56
- // 设置下拉框选中状态
57
- document.getElementById('version-a').value = currentVersionA;
58
- document.getElementById('version-b').value = currentVersionB;
59
- await performDiff();
60
- }
61
-
62
- // 6. 更新文件路径显示
63
- updateFilePathDisplay();
64
- });
65
-
66
- /**
67
- * 加载 Skill 信息
68
- */
69
- async function loadSkillInfo() {
70
- try {
71
- const skill = await apiGet(`/skills/${skillId}`);
72
- currentSkill = skill;
73
-
74
- // 更新页面标题
75
- document.title = `${t('diff.pageTitle')}${skill.name} - Skill Base`;
76
-
77
- // 更新面包屑
78
- const breadcrumbSkill = document.getElementById('breadcrumb-skill');
79
- breadcrumbSkill.textContent = skill.name;
80
- breadcrumbSkill.href = `/skill.html?id=${encodeURIComponent(skillId)}`;
81
-
82
- } catch (error) {
83
- console.error('Failed to load Skill info:', error);
84
- showToast(t('diff.loadInfoFailed'), 'error');
85
- }
86
- }
87
-
88
- /**
89
- * 加载版本列表
90
- */
91
- async function loadVersions() {
92
- try {
93
- const data = await apiGet(`/skills/${skillId}/versions`);
94
- versions = data.versions || [];
95
-
96
- // 填充两个版本下拉框
97
- const selectA = document.getElementById('version-a');
98
- const selectB = document.getElementById('version-b');
99
-
100
- const optionsHtml = versions.map((v, index) => {
101
- const label = index === 0 ? `${v.version} (${t('skill.latestTag').replace(/[()]/g,'')})` : v.version;
102
- return `<option value="${escapeHtml(v.version)}">${escapeHtml(label)}</option>`;
103
- }).join('');
104
-
105
- selectA.innerHTML = `<option value="">${t('diff.selectVersion')}</option>` + optionsHtml;
106
- selectB.innerHTML = `<option value="">${t('diff.selectVersion')}</option>` + optionsHtml;
107
-
108
- // 恢复选中状态
109
- if (currentVersionA) selectA.value = currentVersionA;
110
- if (currentVersionB) selectB.value = currentVersionB;
111
-
112
- } catch (error) {
113
- console.error('Failed to load version list:', error);
114
- showToast(t('diff.loadVersionsFailed'), 'error');
115
- }
116
- }
117
-
118
- /**
119
- * 版本选择变更事件
120
- */
121
- function onVersionChange() {
122
- const selectA = document.getElementById('version-a');
123
- const selectB = document.getElementById('version-b');
124
- currentVersionA = selectA.value;
125
- currentVersionB = selectB.value;
126
-
127
- // 更新 URL
128
- updateUrl();
129
- }
130
-
131
- /**
132
- * 更新 URL(不刷新页面)
133
- */
134
- function updateUrl() {
135
- const url = new URL(window.location);
136
- if (currentVersionA) {
137
- url.searchParams.set('version_a', currentVersionA);
138
- }
139
- if (currentVersionB) {
140
- url.searchParams.set('version_b', currentVersionB);
141
- }
142
- if (currentFilePath) {
143
- url.searchParams.set('path', currentFilePath);
144
- } else {
145
- url.searchParams.delete('path');
146
- }
147
- window.history.replaceState({}, '', url);
148
- }
149
-
150
- /**
151
- * 更新文件路径显示
152
- */
153
- function updateFilePathDisplay() {
154
- const pathDisplay = document.getElementById('current-file-path');
155
- if (currentFilePath) {
156
- pathDisplay.textContent = currentFilePath;
157
- } else {
158
- pathDisplay.textContent = t('diff.allFiles');
159
- }
160
- }
161
-
162
- /**
163
- * 执行 Diff
164
- */
165
- async function performDiff() {
166
- if (!currentVersionA || !currentVersionB) {
167
- showToast(t('diff.selectBoth'), 'warning');
168
- return;
169
- }
170
-
171
- if (currentVersionA === currentVersionB) {
172
- showToast(t('diff.selectBoth'), 'warning');
173
- return;
174
- }
175
-
176
- const outputContainer = document.getElementById('diff-output-container');
177
- const outputDiv = document.getElementById('diff-output');
178
-
179
- // 显示 loading
180
- outputDiv.innerHTML = `
181
- <div class="loading-content">
182
- <div class="spinner"></div>
183
- <p style="margin-top: var(--spacing-md); color: var(--text-secondary);">${t('diff.computing')}</p>
184
- </div>
185
- `;
186
-
187
- try {
188
- // 1. 并发下载两个版本的 zip
189
- const [bufA, bufB] = await Promise.all([
190
- fetch(`${API_BASE}/skills/${skillId}/versions/${currentVersionA}/download`, { credentials: 'same-origin' }).then(r => {
191
- if (!r.ok) throw new Error(`Failed to download version ${currentVersionA}`);
192
- return r.arrayBuffer();
193
- }),
194
- fetch(`${API_BASE}/skills/${skillId}/versions/${currentVersionB}/download`, { credentials: 'same-origin' }).then(r => {
195
- if (!r.ok) throw new Error(`Failed to download version ${currentVersionB}`);
196
- return r.arrayBuffer();
197
- })
198
- ]);
199
-
200
- // 2. JSZip 解压
201
- [zipA, zipB] = await Promise.all([
202
- JSZip.loadAsync(bufA),
203
- JSZip.loadAsync(bufB)
204
- ]);
205
-
206
- if (currentFilePath) {
207
- // 单文件 diff
208
- document.getElementById('changed-files').style.display = 'none';
209
- await diffSingleFile(currentFilePath);
210
- } else {
211
- // 整体对比:列出所有变更的文件
212
- await diffAllFiles();
213
- }
214
-
215
- } catch (error) {
216
- console.error('Diff failed:', error);
217
- showToast(t('diff.computeFailed') + ': ' + error.message, 'error');
218
- outputDiv.innerHTML = `
219
- <div class="empty-state">
220
- <div class="empty-state-icon">❌</div>
221
- <p>${t('diff.computeFailed')}: ${escapeHtml(error.message)}</p>
222
- </div>
223
- `;
224
- }
225
- }
226
-
227
- /**
228
- * 单文件 Diff
229
- * @param {string} filePath - 文件路径
230
- */
231
- async function diffSingleFile(filePath) {
232
- const outputDiv = document.getElementById('diff-output');
233
- const statsDiv = document.getElementById('diff-stats');
234
-
235
- // 检查是否为二进制文件
236
- if (isBinaryExt(filePath)) {
237
- outputDiv.innerHTML = `
238
- <div class="binary-diff-notice">
239
- <div class="empty-state-icon">📦</div>
240
- <p>${t('diff.binaryDiff')}</p>
241
- <p class="text-muted mt-1">${escapeHtml(filePath)}</p>
242
- </div>
243
- `;
244
- statsDiv.style.display = 'none';
245
- return;
246
- }
247
-
248
- try {
249
- const fileA = zipA.file(filePath);
250
- const fileB = zipB.file(filePath);
251
-
252
- const contentA = fileA ? await fileA.async('string') : '';
253
- const contentB = fileB ? await fileB.async('string') : '';
254
-
255
- // 如果内容完全相同
256
- if (contentA === contentB) {
257
- outputDiv.innerHTML = `
258
- <div class="empty-state">
259
- <div class="empty-state-icon">✅</div>
260
- <p>Files are identical, no differences</p>
261
- </div>
262
- `;
263
- statsDiv.style.display = 'none';
264
- return;
265
- }
266
-
267
- // 使用 Diff.createPatch 生成 unified diff
268
- const patch = Diff.createPatch(
269
- filePath,
270
- contentA,
271
- contentB,
272
- `Version ${currentVersionA}`,
273
- `Version ${currentVersionB}`
274
- );
275
-
276
- // 使用 diff2html 渲染
277
- renderDiff(patch);
278
-
279
- // 更新统计信息
280
- updateDiffStats(patch, 1);
281
- statsDiv.style.display = 'flex';
282
-
283
- } catch (error) {
284
- console.error('单文件 Diff 失败:', error);
285
- outputDiv.innerHTML = `
286
- <div class="empty-state">
287
- <div class="empty-state-icon">❌</div>
288
- <p>对比失败: ${escapeHtml(error.message)}</p>
289
- </div>
290
- `;
291
- }
292
- }
293
-
294
- /**
295
- * 全文件 Diff(列出所有变更文件)
296
- */
297
- async function diffAllFiles() {
298
- const outputDiv = document.getElementById('diff-output');
299
- const statsDiv = document.getElementById('diff-stats');
300
- const changedFilesDiv = document.getElementById('changed-files');
301
- const changedFilesListDiv = document.getElementById('changed-files-list');
302
-
303
- // 收集所有文件
304
- const filesA = new Set();
305
- const filesB = new Set();
306
-
307
- zipA.forEach((path, entry) => {
308
- if (!entry.dir) filesA.add(path);
309
- });
310
-
311
- zipB.forEach((path, entry) => {
312
- if (!entry.dir) filesB.add(path);
313
- });
314
-
315
- const allFiles = new Set([...filesA, ...filesB]);
316
- changedFiles = [];
317
-
318
- // 检查每个文件的变更状态
319
- for (const file of allFiles) {
320
- const inA = filesA.has(file);
321
- const inB = filesB.has(file);
322
-
323
- let status;
324
- if (!inA && inB) {
325
- status = 'added';
326
- } else if (inA && !inB) {
327
- status = 'deleted';
328
- } else {
329
- // 比较内容
330
- const contentA = await zipA.file(file)?.async('string') ?? '';
331
- const contentB = await zipB.file(file)?.async('string') ?? '';
332
- if (contentA !== contentB) {
333
- status = 'modified';
334
- } else {
335
- continue; // 内容相同,跳过
336
- }
337
- }
338
-
339
- changedFiles.push({ file, status });
340
- }
341
-
342
- // 排序:按状态和文件名
343
- changedFiles.sort((a, b) => {
344
- const statusOrder = { added: 0, deleted: 1, modified: 2 };
345
- if (statusOrder[a.status] !== statusOrder[b.status]) {
346
- return statusOrder[a.status] - statusOrder[b.status];
347
- }
348
- return a.file.localeCompare(b.file);
349
- });
350
-
351
- // 如果没有变更
352
- if (changedFiles.length === 0) {
353
- outputDiv.innerHTML = `
354
- <div class="empty-state">
355
- <div class="empty-state-icon">✅</div>
356
- <p>The two versions are identical, no differences</p>
357
- </div>
358
- `;
359
- statsDiv.style.display = 'none';
360
- changedFilesDiv.style.display = 'none';
361
- return;
362
- }
363
-
364
- // 渲染变更文件列表
365
- renderChangedFileList(changedFiles);
366
- changedFilesDiv.style.display = 'block';
367
-
368
- // 生成所有文件的合并 diff
369
- let combinedPatch = '';
370
- for (const change of changedFiles) {
371
- if (isBinaryExt(change.file)) {
372
- combinedPatch += `diff --git a/${change.file} b/${change.file}\n`;
373
- combinedPatch += `Binary files differ\n`;
374
- continue;
375
- }
376
-
377
- const contentA = await zipA.file(change.file)?.async('string') ?? '';
378
- const contentB = await zipB.file(change.file)?.async('string') ?? '';
379
-
380
- const patch = Diff.createPatch(
381
- change.file,
382
- contentA,
383
- contentB,
384
- `Version ${currentVersionA}`,
385
- `Version ${currentVersionB}`
386
- );
387
- combinedPatch += patch;
388
- }
389
-
390
- // 渲染 diff
391
- renderDiff(combinedPatch);
392
-
393
- // 更新统计信息
394
- updateDiffStats(combinedPatch, changedFiles.length);
395
- statsDiv.style.display = 'flex';
396
- }
397
-
398
- /**
399
- * 渲染变更文件列表
400
- * @param {array} changes - 变更文件数组
401
- */
402
- function renderChangedFileList(changes) {
403
- const listDiv = document.getElementById('changed-files-list');
404
-
405
- const statusLabels = {
406
- added: t('diff.added'),
407
- deleted: t('diff.deleted'),
408
- modified: t('diff.modified')
409
- };
410
-
411
- listDiv.innerHTML = changes.map((change, index) => `
412
- <div class="changed-file-item" data-file="${escapeHtml(change.file)}" onclick="selectChangedFile('${escapeHtml(change.file)}', ${index})">
413
- <span class="changed-file-status ${change.status}">${statusLabels[change.status]}</span>
414
- <span class="changed-file-path">${escapeHtml(change.file)}</span>
415
- </div>
416
- `).join('');
417
- }
418
-
419
- /**
420
- * 选择变更文件进行单独 diff
421
- * @param {string} filePath - 文件路径
422
- * @param {number} index - 索引
423
- */
424
- async function selectChangedFile(filePath, index) {
425
- // 更新选中状态
426
- document.querySelectorAll('.changed-file-item').forEach((el, i) => {
427
- el.classList.toggle('active', i === index);
428
- });
429
-
430
- currentFilePath = filePath;
431
- updateFilePathDisplay();
432
- updateUrl();
433
-
434
- await diffSingleFile(filePath);
435
- }
436
-
437
- /**
438
- * 渲染 Diff(使用 diff2html)
439
- * @param {string} patch - unified diff 字符串
440
- */
441
- function renderDiff(patch) {
442
- const outputDiv = document.getElementById('diff-output');
443
-
444
- // 使用 Diff2HtmlUI API
445
- const targetElement = document.getElementById('diff-output');
446
- const configuration = {
447
- drawFileList: false,
448
- matching: 'lines',
449
- outputFormat: outputFormat, // 'side-by-side' 或 'line-by-line'
450
- renderNothingWhenEmpty: false
451
- };
452
-
453
- const diff2htmlUi = new Diff2HtmlUI(targetElement, patch, configuration);
454
- diff2htmlUi.draw();
455
- }
456
-
457
- /**
458
- * 更新 Diff 统计
459
- * @param {string} patch - unified diff 字符串
460
- * @param {number} fileCount - 文件数量
461
- */
462
- function updateDiffStats(patch, fileCount) {
463
- let added = 0;
464
- let removed = 0;
465
-
466
- patch.split('\n').forEach(line => {
467
- if (line.startsWith('+') && !line.startsWith('+++')) {
468
- added++;
469
- }
470
- if (line.startsWith('-') && !line.startsWith('---')) {
471
- removed++;
472
- }
473
- });
474
-
475
- document.getElementById('stats-added').textContent = `+${added} lines`;
476
- document.getElementById('stats-removed').textContent = `-${removed} lines`;
477
- document.getElementById('stats-files').textContent = `${fileCount} ${t('diff.filesChanged')}`;
478
- }
479
-
480
- /**
481
- * 切换视图格式
482
- * @param {string} format - 'side-by-side' 或 'line-by-line'
483
- */
484
- function toggleOutputFormat(format) {
485
- outputFormat = format;
486
-
487
- // 更新按钮状态
488
- document.getElementById('btn-side-by-side').classList.toggle('active', format === 'side-by-side');
489
- document.getElementById('btn-line-by-line').classList.toggle('active', format === 'line-by-line');
490
-
491
- // 如果已经有 diff 结果,重新渲染
492
- if (zipA && zipB) {
493
- if (currentFilePath) {
494
- diffSingleFile(currentFilePath);
495
- } else if (changedFiles.length > 0) {
496
- // 重新生成合并 diff
497
- performDiff();
498
- }
499
- }
500
- }
501
-
502
- /**
503
- * 判断是否为二进制文件扩展名
504
- * @param {string} filePath - 文件路径
505
- * @returns {boolean}
506
- */
507
- function isBinaryExt(filePath) {
508
- const ext = '.' + filePath.split('.').pop()?.toLowerCase();
509
- return BINARY_EXTS.has(ext);
510
- }
511
-
512
- /**
513
- * 返回 Skill 详情页
514
- */
515
- function goToSkillDetail() {
516
- if (skillId) {
517
- window.location.href = `/skill.html?id=${encodeURIComponent(skillId)}`;
518
- } else {
519
- window.location.href = '/';
520
- }
521
- }
522
-
523
- /**
524
- * 清除文件选择,显示全部变更
525
- */
526
- function clearFileSelection() {
527
- currentFilePath = null;
528
- updateFilePathDisplay();
529
- updateUrl();
530
-
531
- // 清除选中状态
532
- document.querySelectorAll('.changed-file-item').forEach(el => {
533
- el.classList.remove('active');
534
- });
535
-
536
- // 重新执行全文件 diff
537
- if (zipA && zipB) {
538
- diffAllFiles();
539
- }
540
- }