openclacky 1.3.6 → 1.3.8
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/lib/clacky/agent/hook_manager.rb +10 -1
- data/lib/clacky/agent/system_prompt_builder.rb +1 -1
- data/lib/clacky/agent.rb +17 -3
- data/lib/clacky/agent_profile.rb +124 -53
- data/lib/clacky/billing/billing_store.rb +3 -2
- data/lib/clacky/brand_config.rb +309 -6
- data/lib/clacky/cli.rb +4 -57
- data/lib/clacky/default_extensions/coding/agents/coding/avatar.png +0 -0
- data/lib/clacky/default_extensions/coding/ext.yml +19 -0
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/avatar.png +0 -0
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +95 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +254 -0
- data/lib/clacky/default_extensions/ext-studio/ext.yml +33 -0
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1288 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +71 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +74 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +65 -0
- data/lib/clacky/default_extensions/general/agents/general/avatar.png +0 -0
- data/lib/clacky/default_extensions/general/ext.yml +19 -0
- data/lib/clacky/default_extensions/git/ext.yml +12 -0
- data/lib/clacky/{default_agents/_panels/git/panel.js → default_extensions/git/panels/git/view.js} +4 -5
- data/lib/clacky/default_extensions/meeting/{handler.rb → api/handler.rb} +78 -26
- data/lib/clacky/default_extensions/meeting/ext.yml +15 -0
- data/lib/clacky/default_extensions/meeting/{meeting.js → panels/meeting/view.js} +266 -90
- data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +2 -1
- data/lib/clacky/default_extensions/time_machine/ext.yml +12 -0
- data/lib/clacky/{default_agents/_panels/time_machine/panel.js → default_extensions/time_machine/panels/time_machine/view.js} +58 -47
- data/lib/clacky/default_skills/channel-manager/discord_setup.rb +1 -1
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +83 -18
- data/lib/clacky/{api_extension.rb → extension/api_extension.rb} +27 -11
- data/lib/clacky/extension/api_loader.rb +136 -0
- data/lib/clacky/extension/cli_commands.rb +226 -0
- data/lib/clacky/{server/api_extension_dispatcher.rb → extension/dispatcher.rb} +22 -9
- data/lib/clacky/extension/hook_loader.rb +77 -0
- data/lib/clacky/extension/loader.rb +489 -0
- data/lib/clacky/extension/packager.rb +226 -0
- data/lib/clacky/{patch_loader.rb → extension/patch_loader.rb} +45 -0
- data/lib/clacky/extension/scaffold/template_renderer.rb +64 -0
- data/lib/clacky/extension/scaffold/templates/full/README.md.erb +48 -0
- data/lib/clacky/extension/scaffold/templates/full/agents/designer.md +5 -0
- data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
- data/lib/clacky/extension/scaffold/templates/full/channels/noop.rb.erb +40 -0
- data/lib/clacky/extension/scaffold/templates/full/ext.yml.erb +41 -0
- data/lib/clacky/extension/scaffold/templates/full/hooks/audit.rb +9 -0
- data/lib/clacky/extension/scaffold/templates/full/panels/dashboard/view.js.erb +194 -0
- data/lib/clacky/extension/scaffold/templates/full/patches/audit.rb +15 -0
- data/lib/clacky/extension/scaffold/templates/full/skills/__slug__-skill/SKILL.md.erb +8 -0
- data/lib/clacky/extension/scaffold/templates/hello/api/handler.rb.erb +9 -0
- data/lib/clacky/extension/scaffold/templates/hello/ext.yml.erb +12 -0
- data/lib/clacky/extension/scaffold/templates/hello/panels/hello/view.js.erb +31 -0
- data/lib/clacky/extension/scaffold.rb +55 -0
- data/lib/clacky/extension/verifier.rb +196 -0
- data/lib/clacky/identity.rb +70 -0
- data/lib/clacky/locales/en.rb +19 -1
- data/lib/clacky/locales/zh.rb +19 -1
- data/lib/clacky/platform_http_client.rb +18 -15
- data/lib/clacky/server/channel/extension_adapter_loader.rb +32 -0
- data/lib/clacky/server/channel.rb +6 -0
- data/lib/clacky/server/http_server.rb +439 -146
- data/lib/clacky/server/session_registry.rb +9 -2
- data/lib/clacky/shell_hook_loader.rb +266 -22
- data/lib/clacky/skill_loader.rb +28 -1
- data/lib/clacky/ui2/components/welcome_banner.rb +1 -1
- data/lib/clacky/ui2/ui_controller.rb +9 -6
- data/lib/clacky/utils/workspace_rules.rb +2 -2
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +1034 -675
- data/lib/clacky/web/app.js +63 -15
- data/lib/clacky/web/auth.js +2 -0
- data/lib/clacky/web/components/notify.js +2 -0
- data/lib/clacky/web/components/sidebar.js +1 -5
- data/lib/clacky/web/core/ext.js +432 -89
- data/lib/clacky/web/features/backup/store.js +1 -0
- data/lib/clacky/web/features/backup/view.js +0 -1
- data/lib/clacky/web/features/brand/view.js +13 -5
- data/lib/clacky/web/features/extensions/store.js +184 -0
- data/lib/clacky/web/features/extensions/view.js +390 -0
- data/lib/clacky/web/features/new-session/store.js +199 -0
- data/lib/clacky/web/features/new-session/view.js +619 -0
- data/lib/clacky/web/features/share/view.js +63 -14
- data/lib/clacky/web/features/skills/store.js +3 -1
- data/lib/clacky/web/features/skills/view.js +1 -1
- data/lib/clacky/web/features/tasks/view.js +12 -0
- data/lib/clacky/web/features/trash/store.js +24 -6
- data/lib/clacky/web/features/trash/view.js +48 -12
- data/lib/clacky/web/features/workspace/store.js +2 -0
- data/lib/clacky/web/features/workspace/view.js +0 -1
- data/lib/clacky/web/i18n.js +152 -106
- data/lib/clacky/web/index.html +172 -190
- data/lib/clacky/web/sessions.js +67 -272
- data/lib/clacky/web/settings.js +1 -1
- data/lib/clacky/web/skills.js +88 -48
- data/lib/clacky/web/theme.js +5 -0
- data/lib/clacky/web/ws-dispatcher.js +12 -2
- data/lib/clacky/web/ws.js +2 -0
- data/lib/clacky.rb +21 -10
- metadata +52 -21
- data/benchmark/runner.rb +0 -441
- data/lib/clacky/api_extension_loader.rb +0 -168
- data/lib/clacky/default_agents/SOUL.md +0 -3
- data/lib/clacky/default_agents/USER.md +0 -1
- data/lib/clacky/default_agents/coding/profile.yml +0 -5
- data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
- data/lib/clacky/default_agents/general/profile.yml +0 -2
- data/lib/clacky/default_extensions/meeting/meta.yml +0 -3
- data/lib/clacky/web/features/creator/store.js +0 -81
- data/lib/clacky/web/features/creator/view.js +0 -380
- /data/lib/clacky/{default_agents → default_extensions/coding/agents}/coding/system_prompt.md +0 -0
- /data/lib/clacky/{default_agents → default_extensions/general/agents}/general/system_prompt.md +0 -0
- /data/lib/clacky/{default_agents/base_prompt.md → prompts/base.md} +0 -0
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
// ───────────────────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
15
|
const ShareView = (() => {
|
|
16
|
+
const THEME_DOT = {
|
|
17
|
+
geek: "#38bdf8",
|
|
18
|
+
light: "#6ee7b7",
|
|
19
|
+
warm: "#ea580c"
|
|
20
|
+
};
|
|
16
21
|
const THEME_KEY = "clacky-share-theme";
|
|
17
22
|
const DRAFT_KEY = "clacky-share-draft";
|
|
18
23
|
|
|
@@ -158,32 +163,49 @@ const ShareView = (() => {
|
|
|
158
163
|
|
|
159
164
|
_paintBackground(ctx, W, H, t, t.bg);
|
|
160
165
|
|
|
166
|
+
// ── Brand name ──
|
|
161
167
|
ctx.fillStyle = t.title;
|
|
162
|
-
ctx.textAlign = "
|
|
163
|
-
ctx.font = "
|
|
164
|
-
ctx.fillText(_brandName(),
|
|
168
|
+
ctx.textAlign = "left";
|
|
169
|
+
ctx.font = "800 72px -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif";
|
|
170
|
+
ctx.fillText(_brandName(), 64, 140);
|
|
171
|
+
|
|
172
|
+
// ── Divider line ──
|
|
173
|
+
ctx.strokeStyle = t.hero;
|
|
174
|
+
ctx.lineWidth = 4;
|
|
175
|
+
ctx.lineCap = "round";
|
|
176
|
+
ctx.beginPath();
|
|
177
|
+
ctx.moveTo(64, 172);
|
|
178
|
+
ctx.lineTo(160, 172);
|
|
179
|
+
ctx.stroke();
|
|
165
180
|
|
|
181
|
+
// ── Copy text ──
|
|
166
182
|
const text = (copy || "").trim() || I18n.t("share.poster.tagline", { brand: _brandName() });
|
|
167
183
|
ctx.fillStyle = t.tagline;
|
|
168
|
-
|
|
184
|
+
ctx.textAlign = "left";
|
|
185
|
+
_drawAutoTextLeft(ctx, text, 64, 210, W - 128, 420);
|
|
169
186
|
|
|
170
187
|
const url = _shareUrl();
|
|
171
188
|
if (url) {
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
const
|
|
189
|
+
// ── QR code — bottom right ──
|
|
190
|
+
const qrSize = 180;
|
|
191
|
+
const qrX = W - qrSize - 56;
|
|
192
|
+
const qrY = H - qrSize - 100;
|
|
175
193
|
ctx.fillStyle = "#ffffff";
|
|
176
|
-
_roundRect(ctx, qrX -
|
|
194
|
+
_roundRect(ctx, qrX - 14, qrY - 14, qrSize + 28, qrSize + 28, 16);
|
|
177
195
|
ctx.fill();
|
|
178
196
|
_drawQrToCanvas(ctx, url, qrX, qrY, qrSize);
|
|
179
197
|
|
|
198
|
+
// ── Scan label under QR ──
|
|
180
199
|
ctx.fillStyle = t.scan;
|
|
181
|
-
ctx.
|
|
182
|
-
ctx.
|
|
200
|
+
ctx.textAlign = "center";
|
|
201
|
+
ctx.font = "400 22px -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif";
|
|
202
|
+
ctx.fillText(I18n.t("share.poster.scan"), qrX + qrSize / 2, qrY + qrSize + 44);
|
|
183
203
|
|
|
204
|
+
// ── Domain — bottom left ──
|
|
184
205
|
ctx.fillStyle = t.scan;
|
|
185
|
-
ctx.
|
|
186
|
-
ctx.
|
|
206
|
+
ctx.textAlign = "left";
|
|
207
|
+
ctx.font = "300 24px -apple-system, sans-serif";
|
|
208
|
+
ctx.fillText(url.replace(/^https?:\/\//, "").replace(/\/$/, ""), 64, H - 56);
|
|
187
209
|
}
|
|
188
210
|
|
|
189
211
|
return canvas.toDataURL("image/png");
|
|
@@ -331,6 +353,27 @@ const ShareView = (() => {
|
|
|
331
353
|
}
|
|
332
354
|
}
|
|
333
355
|
|
|
356
|
+
function _drawAutoTextLeft(ctx, text, x, startY, maxWidth, maxHeight) {
|
|
357
|
+
const sizes = [52, 46, 40, 36, 32, 28, 24];
|
|
358
|
+
let chosen = null;
|
|
359
|
+
for (const size of sizes) {
|
|
360
|
+
ctx.font = "600 " + size + "px -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif";
|
|
361
|
+
const lh = Math.round(size * 1.55);
|
|
362
|
+
const lines = _wrapLines(ctx, text, maxWidth);
|
|
363
|
+
if (lines.length * lh <= maxHeight || size === sizes[sizes.length - 1]) {
|
|
364
|
+
chosen = { size, lh, lines };
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
ctx.font = "600 " + chosen.size + "px -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif";
|
|
369
|
+
ctx.textAlign = "left";
|
|
370
|
+
let y = startY + chosen.lh * 0.8;
|
|
371
|
+
for (const line of chosen.lines) {
|
|
372
|
+
ctx.fillText(line, x, y);
|
|
373
|
+
y += chosen.lh;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
334
377
|
function _wrapLines(ctx, text, maxWidth) {
|
|
335
378
|
const out = [];
|
|
336
379
|
for (const para of String(text).split("\n")) {
|
|
@@ -469,9 +512,10 @@ const ShareView = (() => {
|
|
|
469
512
|
const themeChips = THEME_ORDER.map((id) => {
|
|
470
513
|
const th = THEMES[id];
|
|
471
514
|
const on = id === _themeId() ? " is-active" : "";
|
|
515
|
+
const dot = '<span class="share-theme-dot" style="background:' + THEME_DOT[id] + '"></span>';
|
|
472
516
|
return '<button type="button" class="share-theme-chip' + on + '" data-theme="' + id + '"' +
|
|
473
517
|
' style="background:' + th.swatch + '" title="' + _esc(I18n.t(th.labelKey)) + '">' +
|
|
474
|
-
'<span class="share-theme-name">' + _esc(I18n.t(th.labelKey)) + '</span></button>';
|
|
518
|
+
dot + '<span class="share-theme-name">' + _esc(I18n.t(th.labelKey)) + '</span></button>';
|
|
475
519
|
}).join("");
|
|
476
520
|
|
|
477
521
|
const platformTabs = PLATFORMS.map((p) => {
|
|
@@ -486,7 +530,9 @@ const ShareView = (() => {
|
|
|
486
530
|
'<h3 class="share-title">' + _esc(I18n.t(titleKey, { brand: _brandName() })) + '</h3>' +
|
|
487
531
|
'<p class="share-subtitle">' + _esc(I18n.t(subtitleKey)) + '</p>' +
|
|
488
532
|
'<div class="share-body">' +
|
|
489
|
-
'<div class="share-poster-wrap"
|
|
533
|
+
'<div class="share-poster-wrap">' +
|
|
534
|
+
'<img class="share-poster-img" alt="poster"/>' +
|
|
535
|
+
'</div>' +
|
|
490
536
|
'<div class="share-controls">' +
|
|
491
537
|
'<div class="share-theme-row">' +
|
|
492
538
|
'<span class="share-row-label">' + _esc(I18n.t("share.theme.label")) + '</span>' +
|
|
@@ -517,6 +563,9 @@ const ShareView = (() => {
|
|
|
517
563
|
const img = o.querySelector(".share-poster-img");
|
|
518
564
|
const textarea = o.querySelector(".share-text");
|
|
519
565
|
|
|
566
|
+
img.style.cursor = "pointer";
|
|
567
|
+
img.addEventListener("click", () => _downloadPoster((textarea.value || "").trim()));
|
|
568
|
+
|
|
520
569
|
const renderPoster = () => {
|
|
521
570
|
const copy = (textarea.value || "").trim();
|
|
522
571
|
try { img.src = _scorecard() ? _buildScorecardPoster(copy) : _buildPoster(copy); }
|
|
@@ -30,7 +30,7 @@ const SkillsStore = (() => {
|
|
|
30
30
|
let _brandActivated = false; // whether a license is currently active
|
|
31
31
|
let _freeMode = false; // brand-skills tab is showing free-mode skills
|
|
32
32
|
let _paidSkillsCount = 0; // premium (encrypted) skills locked behind activation
|
|
33
|
-
let _showSystemSkills =
|
|
33
|
+
let _showSystemSkills = localStorage.getItem("skill-ac-show-system") === "true";
|
|
34
34
|
|
|
35
35
|
// ── Internal event bus ──────────────────────────────────────────────────
|
|
36
36
|
// Always live (unlike Clacky.ext, which is silenced under ?pure=true). The
|
|
@@ -173,6 +173,7 @@ const SkillsStore = (() => {
|
|
|
173
173
|
/** Toggle visibility of system skills; emit so the view re-renders. */
|
|
174
174
|
setShowSystemSkills(show) {
|
|
175
175
|
_showSystemSkills = !!show;
|
|
176
|
+
localStorage.setItem("skill-ac-show-system", _showSystemSkills ? "true" : "false");
|
|
176
177
|
_emit("skills:changed", { skills: _skills });
|
|
177
178
|
},
|
|
178
179
|
|
|
@@ -314,3 +315,4 @@ const SkillsStore = (() => {
|
|
|
314
315
|
|
|
315
316
|
// Expose the facade under its historical global name.
|
|
316
317
|
const Skills = SkillsStore;
|
|
318
|
+
Clacky.Skills = Skills;
|
|
@@ -265,7 +265,7 @@ const SkillsView = (() => {
|
|
|
265
265
|
btn.setAttribute("data-i18n", "skills.brand.activateBtn");
|
|
266
266
|
btn.addEventListener("click", () => {
|
|
267
267
|
if (typeof Brand !== "undefined" && Brand.goToLicenseInput) Brand.goToLicenseInput();
|
|
268
|
-
else Router.navigate("settings");
|
|
268
|
+
else window.Clacky.Router.navigate("settings");
|
|
269
269
|
});
|
|
270
270
|
|
|
271
271
|
hint.appendChild(msgEl);
|
|
@@ -76,6 +76,12 @@ const TasksView = (() => {
|
|
|
76
76
|
return isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
// ── every-N-hours on specific days e.g. 0 */3 * * 1-5 ────────────
|
|
80
|
+
if (isAny(dom) && isAny(month) && !isAny(dow) && hour.startsWith("*/")) {
|
|
81
|
+
const dl = dowLabel(dow);
|
|
82
|
+
const ev = isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
|
|
83
|
+
if (dl) return `${dl} ${ev}`;
|
|
84
|
+
}
|
|
79
85
|
// ── every minute ──────────────────────────────────────────────────────
|
|
80
86
|
if (isAny(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
81
87
|
return isZh ? "每分钟" : "Every minute";
|
|
@@ -84,6 +90,12 @@ const TasksView = (() => {
|
|
|
84
90
|
if (isInt(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
85
91
|
return isZh ? `每小时 :${pad(min)}` : `Hourly at :${pad(min)}`;
|
|
86
92
|
}
|
|
93
|
+
// ── hourly at :MM on specific days e.g. 30 * * * 1-5 ─────────────
|
|
94
|
+
if (isInt(min) && isAny(hour) && isAny(dom) && isAny(month) && !isAny(dow)) {
|
|
95
|
+
const dl = dowLabel(dow);
|
|
96
|
+
const tm = isZh ? `每小时 :${pad(min)}` : `Hourly at :${pad(min)}`;
|
|
97
|
+
if (dl) return `${dl} ${tm}`;
|
|
98
|
+
}
|
|
87
99
|
|
|
88
100
|
// ── every-N-min within hour range on certain days e.g. */1 9-14 * * 1-5
|
|
89
101
|
if (min.startsWith("*/") && /^\d+-\d+$/.test(hour) && isAny(dom) && isAny(month)) {
|
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
// ───────────────────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
15
|
const TrashStore = (() => {
|
|
16
|
+
const SOURCES = Object.freeze({ ALL: "all", CRON: "cron", MANUAL: "manual" });
|
|
17
|
+
|
|
16
18
|
let _files = [];
|
|
17
19
|
let _totals = { count: 0, size: 0 };
|
|
18
20
|
let _sessions = [];
|
|
19
21
|
let _sessionTotals = { count: 0, size: 0 };
|
|
22
|
+
let _filterSource = SOURCES.ALL;
|
|
20
23
|
let _loading = false;
|
|
21
24
|
|
|
22
25
|
const _listeners = {};
|
|
@@ -44,8 +47,20 @@ const TrashStore = (() => {
|
|
|
44
47
|
const state = {
|
|
45
48
|
get files() { return _files; },
|
|
46
49
|
get totals() { return _totals; },
|
|
47
|
-
get sessions() {
|
|
48
|
-
|
|
50
|
+
get sessions() {
|
|
51
|
+
if (_filterSource === SOURCES.ALL) return _sessions;
|
|
52
|
+
return _filterSource === SOURCES.CRON
|
|
53
|
+
? _sessions.filter(s => s.source === SOURCES.CRON)
|
|
54
|
+
: _sessions.filter(s => s.source !== SOURCES.CRON);
|
|
55
|
+
},
|
|
56
|
+
get sessionTotals() {
|
|
57
|
+
const filtered = _filterSource === SOURCES.ALL ? _sessions
|
|
58
|
+
: _filterSource === SOURCES.CRON
|
|
59
|
+
? _sessions.filter(s => s.source === SOURCES.CRON)
|
|
60
|
+
: _sessions.filter(s => s.source !== SOURCES.CRON);
|
|
61
|
+
return { count: filtered.length, size: filtered.reduce((sum, s) => sum + (s.file_size || 0), 0) };
|
|
62
|
+
},
|
|
63
|
+
get filterSource() { return _filterSource; },
|
|
49
64
|
orphanCount() { return _files.filter(f => _isOrphanRoot(f.project_root)).length; },
|
|
50
65
|
isOrphan(file) { return _isOrphanRoot(file.project_root); },
|
|
51
66
|
};
|
|
@@ -53,6 +68,13 @@ const TrashStore = (() => {
|
|
|
53
68
|
const Trash = {
|
|
54
69
|
on: _on,
|
|
55
70
|
state,
|
|
71
|
+
SOURCES,
|
|
72
|
+
|
|
73
|
+
setFilterSource(source) {
|
|
74
|
+
if (source === _filterSource) return;
|
|
75
|
+
_filterSource = source;
|
|
76
|
+
_emit("trash:sessionsChanged");
|
|
77
|
+
},
|
|
56
78
|
|
|
57
79
|
async loadFiles() {
|
|
58
80
|
if (_loading) return;
|
|
@@ -230,10 +252,6 @@ const TrashStore = (() => {
|
|
|
230
252
|
|
|
231
253
|
function _removeSessionLocal(session) {
|
|
232
254
|
_sessions = _sessions.filter(s => s.session_id !== session.session_id);
|
|
233
|
-
_sessionTotals = {
|
|
234
|
-
count: Math.max(0, _sessionTotals.count - 1),
|
|
235
|
-
size: Math.max(0, _sessionTotals.size - (session.file_size || 0))
|
|
236
|
-
};
|
|
237
255
|
}
|
|
238
256
|
|
|
239
257
|
return Trash;
|
|
@@ -61,8 +61,13 @@ const TrashView = (() => {
|
|
|
61
61
|
|
|
62
62
|
const filePane = $("trash-tab-file");
|
|
63
63
|
const sessionPane = $("trash-tab-session");
|
|
64
|
+
const filterBar = $("trash-filter-bar");
|
|
64
65
|
if (filePane) filePane.style.display = tab === "file-trash" ? "" : "none";
|
|
65
66
|
if (sessionPane) sessionPane.style.display = tab === "session-trash" ? "" : "none";
|
|
67
|
+
if (filterBar) filterBar.style.display = tab === "session-trash" ? "" : "none";
|
|
68
|
+
|
|
69
|
+
const hint = $("trash-session-hint");
|
|
70
|
+
if (hint) hint.style.display = tab === "session-trash" ? "" : "none";
|
|
66
71
|
|
|
67
72
|
const btnOrphans = $("btn-trash-empty-orphans");
|
|
68
73
|
if (btnOrphans) btnOrphans.style.display = tab === "file-trash" ? "" : "none";
|
|
@@ -72,12 +77,8 @@ const TrashView = (() => {
|
|
|
72
77
|
|
|
73
78
|
function _load() {
|
|
74
79
|
if (_activeTab === "file-trash") {
|
|
75
|
-
const list = $("trash-list");
|
|
76
|
-
if (list) list.innerHTML = `<div class="creator-loading">${_t("trash.loading")}</div>`;
|
|
77
80
|
Trash.loadFiles();
|
|
78
81
|
} else {
|
|
79
|
-
const list = $("trash-session-list");
|
|
80
|
-
if (list) list.innerHTML = `<div class="creator-loading">${_t("trash.loading")}</div>`;
|
|
81
82
|
Trash.loadSessions();
|
|
82
83
|
}
|
|
83
84
|
}
|
|
@@ -109,7 +110,7 @@ const TrashView = (() => {
|
|
|
109
110
|
if (btnAll) btnAll.disabled = files.length === 0;
|
|
110
111
|
|
|
111
112
|
if (files.length === 0) {
|
|
112
|
-
list.innerHTML = `<div class="
|
|
113
|
+
list.innerHTML = `<div class="trash-empty">${_t("trash.empty")}</div>`;
|
|
113
114
|
return;
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -176,11 +177,34 @@ const TrashView = (() => {
|
|
|
176
177
|
|
|
177
178
|
// ── Session trash rendering ──────────────────────────────────────────
|
|
178
179
|
|
|
180
|
+
function _renderSkeleton() {
|
|
181
|
+
const isSession = _activeTab === "session-trash";
|
|
182
|
+
const list = $(isSession ? "trash-session-list" : "trash-list");
|
|
183
|
+
if (!list) return;
|
|
184
|
+
let html = "";
|
|
185
|
+
for (let i = 0; i < 3; i++) {
|
|
186
|
+
html += `
|
|
187
|
+
<div class="trash-session-skeleton">
|
|
188
|
+
<div class="trash-session-skeleton-info">
|
|
189
|
+
<div class="skel skel-title"></div>
|
|
190
|
+
<div class="skel skel-subtitle"></div>
|
|
191
|
+
<div class="skel skel-text-sm"></div>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="trash-skeleton-actions">
|
|
194
|
+
<div class="skel trash-skeleton-btn"></div>
|
|
195
|
+
<div class="skel trash-skeleton-btn"></div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>`;
|
|
198
|
+
}
|
|
199
|
+
list.innerHTML = html;
|
|
200
|
+
}
|
|
201
|
+
|
|
179
202
|
function _renderSessions() {
|
|
180
|
-
const list
|
|
181
|
-
const summary
|
|
182
|
-
const btnOld
|
|
183
|
-
const btnAll
|
|
203
|
+
const list = $("trash-session-list");
|
|
204
|
+
const summary = $("trash-summary");
|
|
205
|
+
const btnOld = $("btn-trash-empty-old");
|
|
206
|
+
const btnAll = $("btn-trash-empty-all");
|
|
207
|
+
const filterBar = $("trash-filter-bar");
|
|
184
208
|
if (!list) return;
|
|
185
209
|
|
|
186
210
|
const sessions = Trash.state.sessions;
|
|
@@ -196,9 +220,10 @@ const TrashView = (() => {
|
|
|
196
220
|
}
|
|
197
221
|
if (btnOld) btnOld.disabled = sessions.length === 0;
|
|
198
222
|
if (btnAll) btnAll.disabled = sessions.length === 0;
|
|
223
|
+
if (filterBar) filterBar.style.display = "";
|
|
199
224
|
|
|
200
225
|
if (sessions.length === 0) {
|
|
201
|
-
list.innerHTML = `<div class="
|
|
226
|
+
list.innerHTML = `<div class="trash-empty">${_t("trash.noSessionTrash")}</div>`;
|
|
202
227
|
return;
|
|
203
228
|
}
|
|
204
229
|
|
|
@@ -385,18 +410,29 @@ const TrashView = (() => {
|
|
|
385
410
|
if (btnAll) btnAll.addEventListener("click",
|
|
386
411
|
() => _emptyBulk(0, _activeTab === "session-trash"
|
|
387
412
|
? "trash.confirmEmptySessionAll" : "trash.confirmEmptyAll"));
|
|
413
|
+
|
|
414
|
+
// Filter chips
|
|
415
|
+
document.querySelectorAll(".trash-filter-chip").forEach(chip => {
|
|
416
|
+
chip.addEventListener("click", () => {
|
|
417
|
+
const val = chip.dataset.filter;
|
|
418
|
+
Trash.setFilterSource(val);
|
|
419
|
+
document.querySelectorAll(".trash-filter-chip").forEach(c => c.classList.toggle("active", c.dataset.filter === val));
|
|
420
|
+
});
|
|
421
|
+
});
|
|
388
422
|
}
|
|
389
423
|
|
|
390
424
|
function _subscribe() {
|
|
425
|
+
Trash.on("trash:filesLoading", _renderSkeleton);
|
|
426
|
+
Trash.on("trash:sessionsLoading", _renderSkeleton);
|
|
391
427
|
Trash.on("trash:filesChanged", _renderFiles);
|
|
392
428
|
Trash.on("trash:sessionsChanged", _renderSessions);
|
|
393
429
|
Trash.on("trash:filesError", (e) => {
|
|
394
430
|
const list = $("trash-list");
|
|
395
|
-
if (list) list.innerHTML = `<div class="
|
|
431
|
+
if (list) list.innerHTML = `<div class="trash-empty trash-error">${escapeHtml(e.message)}</div>`;
|
|
396
432
|
});
|
|
397
433
|
Trash.on("trash:sessionsError", (e) => {
|
|
398
434
|
const list = $("trash-session-list");
|
|
399
|
-
if (list) list.innerHTML = `<div class="
|
|
435
|
+
if (list) list.innerHTML = `<div class="trash-empty trash-error">${escapeHtml(e.message)}</div>`;
|
|
400
436
|
});
|
|
401
437
|
}
|
|
402
438
|
|
|
@@ -302,4 +302,3 @@ if (window.Clacky && Clacky.ext) {
|
|
|
302
302
|
// Workspace.onSession on every session switch). Rendering is driven by the
|
|
303
303
|
// slot re-render, so this only updates state.
|
|
304
304
|
Workspace.onSession = (session) => { Workspace.setSession(session); };
|
|
305
|
-
window.Workspace = Workspace;
|