openclacky 1.3.5 → 1.3.7
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 +42 -0
- data/lib/clacky/agent/message_compressor.rb +32 -8
- data/lib/clacky/agent/message_compressor_helper.rb +113 -12
- data/lib/clacky/agent.rb +8 -4
- data/lib/clacky/agent_profile.rb +121 -53
- data/lib/clacky/brand_config.rb +309 -6
- data/lib/clacky/cli.rb +4 -58
- data/lib/clacky/default_extensions/coding/ext.yml +18 -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 +32 -0
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1269 -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/ext.yml +18 -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} +1 -2
- 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 -89
- 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 +483 -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 +435 -263
- data/lib/clacky/server/session_registry.rb +59 -3
- data/lib/clacky/session_manager.rb +35 -4
- data/lib/clacky/skill_loader.rb +28 -1
- data/lib/clacky/ui2/layout_manager.rb +0 -5
- data/lib/clacky/ui2/progress_handle.rb +0 -3
- data/lib/clacky/ui2/ui_controller.rb +9 -15
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +782 -554
- 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 +2 -8
- data/lib/clacky/web/core/ext.js +316 -79
- data/lib/clacky/web/features/backup/store.js +24 -0
- data/lib/clacky/web/features/backup/view.js +49 -23
- 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 +389 -0
- data/lib/clacky/web/features/new-session/store.js +186 -0
- data/lib/clacky/web/features/new-session/view.js +319 -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 +88 -27
- data/lib/clacky/web/features/trash/store.js +24 -6
- data/lib/clacky/web/features/trash/view.js +46 -9
- 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 +140 -108
- data/lib/clacky/web/index.html +190 -214
- data/lib/clacky/web/sessions.js +99 -304
- data/lib/clacky/web/settings.js +1 -1
- data/lib/clacky/web/skills.js +2 -0
- data/lib/clacky/web/theme.js +2 -0
- data/lib/clacky/web/ws-dispatcher.js +2 -1
- data/lib/clacky/web/ws.js +2 -0
- data/lib/clacky.rb +21 -10
- metadata +55 -24
- 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
|
@@ -16,58 +16,119 @@ const TasksView = (() => {
|
|
|
16
16
|
if (!cron) return cron;
|
|
17
17
|
const parts = cron.trim().split(/\s+/);
|
|
18
18
|
if (parts.length !== 5) return cron;
|
|
19
|
-
|
|
19
|
+
let [min, hour, dom, month, dow] = parts;
|
|
20
|
+
|
|
21
|
+
// normalize */1 → *
|
|
22
|
+
if (min === "*/1") min = "*";
|
|
23
|
+
if (hour === "*/1") hour = "*";
|
|
24
|
+
if (dom === "*/1") dom = "*";
|
|
25
|
+
if (month === "*/1") month = "*";
|
|
26
|
+
if (dow === "*/1") dow = "*";
|
|
20
27
|
|
|
21
28
|
const isAny = v => v === "*";
|
|
22
|
-
const
|
|
29
|
+
const isInt = v => /^\d+$/.test(v);
|
|
23
30
|
const pad = n => String(n).padStart(2, "0");
|
|
24
31
|
|
|
25
32
|
const lang = (typeof I18n !== "undefined" && I18n.lang()) || "zh";
|
|
26
33
|
const isZh = lang === "zh";
|
|
27
34
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
const DOW_ZH = ["周日","周一","周二","周三","周四","周五","周六"];
|
|
36
|
+
const DOW_EN = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
|
|
37
|
+
const MON_ZH = ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"];
|
|
38
|
+
const MON_EN = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
// dow → label, e.g. "1-5"→"工作日", "1,3,5"→"周一、周三、周五", "1"→"每周一"
|
|
41
|
+
function dowLabel(d) {
|
|
42
|
+
if (isAny(d)) return null;
|
|
43
|
+
if (d === "1-5") return isZh ? "工作日" : "Weekdays";
|
|
44
|
+
if (d === "0,6" || d === "6,0") return isZh ? "周末" : "Weekends";
|
|
45
|
+
if (isInt(d)) {
|
|
46
|
+
const name = (isZh ? DOW_ZH : DOW_EN)[parseInt(d, 10)] || d;
|
|
47
|
+
return isZh ? `每${name}` : name;
|
|
48
|
+
}
|
|
49
|
+
if (/^[\d,]+$/.test(d)) {
|
|
50
|
+
const names = d.split(",").map(n => (isZh ? DOW_ZH : DOW_EN)[parseInt(n, 10)] || n);
|
|
51
|
+
return isZh ? names.join("、") : names.join("/");
|
|
52
|
+
}
|
|
53
|
+
return d;
|
|
54
|
+
}
|
|
35
55
|
|
|
36
|
-
|
|
56
|
+
// build HH:MM string; supports single hour or comma-list like "10,14"
|
|
57
|
+
function timeStr() {
|
|
58
|
+
if (!isInt(min)) return null;
|
|
59
|
+
if (isInt(hour)) return `${pad(hour)}:${pad(min)}`;
|
|
60
|
+
if (/^[\d,]+$/.test(hour))
|
|
61
|
+
return hour.split(",").map(h => `${pad(h)}:${pad(min)}`).join(isZh ? "、" : "/");
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
37
64
|
|
|
65
|
+
// ── every-N-minutes ───────────────────────────────────────────────────
|
|
38
66
|
if (min.startsWith("*/") && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
39
67
|
const n = min.slice(2);
|
|
40
68
|
return isZh ? `每 ${n} 分钟` : `Every ${n} min`;
|
|
41
69
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
// ── every-N-hours ─────────────────────────────────────────────────────
|
|
71
|
+
if (isAny(dom) && isAny(month) && isAny(dow)) {
|
|
72
|
+
if (isAny(min) && hour.startsWith("*/")) {
|
|
73
|
+
return isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
|
|
74
|
+
}
|
|
75
|
+
if (isInt(min) && hour.startsWith("*/")) {
|
|
76
|
+
return isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
|
|
77
|
+
}
|
|
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}`;
|
|
45
84
|
}
|
|
85
|
+
// ── every minute ──────────────────────────────────────────────────────
|
|
46
86
|
if (isAny(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
47
87
|
return isZh ? "每分钟" : "Every minute";
|
|
48
88
|
}
|
|
49
|
-
|
|
89
|
+
// ── hourly at :MM ─────────────────────────────────────────────────────
|
|
90
|
+
if (isInt(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
50
91
|
return isZh ? `每小时 :${pad(min)}` : `Hourly at :${pad(min)}`;
|
|
51
92
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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}`;
|
|
55
98
|
}
|
|
56
|
-
|
|
57
|
-
|
|
99
|
+
|
|
100
|
+
// ── every-N-min within hour range on certain days e.g. */1 9-14 * * 1-5
|
|
101
|
+
if (min.startsWith("*/") && /^\d+-\d+$/.test(hour) && isAny(dom) && isAny(month)) {
|
|
102
|
+
const n = min.slice(2);
|
|
103
|
+
const dl = dowLabel(dow);
|
|
104
|
+
const hr = isZh ? `${hour}时` : `${hour}h`;
|
|
105
|
+
const ev = isZh ? `每 ${n} 分钟` : `Every ${n} min`;
|
|
106
|
+
return dl ? `${dl} ${hr} ${ev}` : `${hr} ${ev}`;
|
|
58
107
|
}
|
|
59
|
-
|
|
60
|
-
|
|
108
|
+
// ── every minute within hour range e.g. * 9-14 * * 1-5
|
|
109
|
+
if (isAny(min) && /^\d+-\d+$/.test(hour) && isAny(dom) && isAny(month)) {
|
|
110
|
+
const dl = dowLabel(dow);
|
|
111
|
+
const hr = isZh ? `${hour}时` : `${hour}h`;
|
|
112
|
+
const ev = isZh ? "每分钟" : "Every minute";
|
|
113
|
+
return dl ? `${dl} ${hr} ${ev}` : `${hr} ${ev}`;
|
|
61
114
|
}
|
|
62
|
-
|
|
63
|
-
|
|
115
|
+
|
|
116
|
+
const ts = timeStr();
|
|
117
|
+
const dl = dowLabel(dow);
|
|
118
|
+
|
|
119
|
+
// ── fixed time, variable days ─────────────────────────────────────────
|
|
120
|
+
if (ts && isAny(dom) && isAny(month)) {
|
|
121
|
+
if (dl) return `${dl} ${ts}`;
|
|
122
|
+
return isZh ? `每天 ${ts}` : `Daily ${ts}`;
|
|
64
123
|
}
|
|
65
|
-
|
|
66
|
-
|
|
124
|
+
// ── fixed time, fixed day-of-month ────────────────────────────────────
|
|
125
|
+
if (ts && isInt(dom) && isAny(month) && isAny(dow)) {
|
|
126
|
+
return isZh ? `每月 ${dom} 日 ${ts}` : `Monthly day ${dom} ${ts}`;
|
|
67
127
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
128
|
+
// ── fixed time, fixed date ────────────────────────────────────────────
|
|
129
|
+
if (ts && isInt(dom) && isInt(month) && isAny(dow)) {
|
|
130
|
+
const m = (isZh ? MON_ZH : MON_EN)[parseInt(month, 10) - 1] || month;
|
|
131
|
+
return isZh ? `${m}${dom}日 ${ts}` : `${m} ${dom} ${ts}`;
|
|
71
132
|
}
|
|
72
133
|
|
|
73
134
|
return cron;
|
|
@@ -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";
|
|
@@ -73,7 +78,7 @@ const TrashView = (() => {
|
|
|
73
78
|
function _load() {
|
|
74
79
|
if (_activeTab === "file-trash") {
|
|
75
80
|
const list = $("trash-list");
|
|
76
|
-
if (list) list.innerHTML = `<div class="
|
|
81
|
+
if (list) list.innerHTML = `<div class="trash-loading">${_t("trash.loading")}</div>`;
|
|
77
82
|
Trash.loadFiles();
|
|
78
83
|
} else {
|
|
79
84
|
const list = $("trash-session-list");
|
|
@@ -109,7 +114,7 @@ const TrashView = (() => {
|
|
|
109
114
|
if (btnAll) btnAll.disabled = files.length === 0;
|
|
110
115
|
|
|
111
116
|
if (files.length === 0) {
|
|
112
|
-
list.innerHTML = `<div class="
|
|
117
|
+
list.innerHTML = `<div class="trash-empty">${_t("trash.empty")}</div>`;
|
|
113
118
|
return;
|
|
114
119
|
}
|
|
115
120
|
|
|
@@ -176,11 +181,33 @@ const TrashView = (() => {
|
|
|
176
181
|
|
|
177
182
|
// ── Session trash rendering ──────────────────────────────────────────
|
|
178
183
|
|
|
184
|
+
function _renderSkeleton() {
|
|
185
|
+
const list = $("trash-session-list");
|
|
186
|
+
if (!list) return;
|
|
187
|
+
let html = "";
|
|
188
|
+
for (let i = 0; i < 3; i++) {
|
|
189
|
+
html += `
|
|
190
|
+
<div class="trash-session-skeleton">
|
|
191
|
+
<div class="trash-session-skeleton-info">
|
|
192
|
+
<div class="skel skel-title"></div>
|
|
193
|
+
<div class="skel skel-subtitle"></div>
|
|
194
|
+
<div class="skel skel-text-sm"></div>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="trash-skeleton-actions">
|
|
197
|
+
<div class="skel trash-skeleton-btn"></div>
|
|
198
|
+
<div class="skel trash-skeleton-btn"></div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>`;
|
|
201
|
+
}
|
|
202
|
+
list.innerHTML = html;
|
|
203
|
+
}
|
|
204
|
+
|
|
179
205
|
function _renderSessions() {
|
|
180
|
-
const list
|
|
181
|
-
const summary
|
|
182
|
-
const btnOld
|
|
183
|
-
const btnAll
|
|
206
|
+
const list = $("trash-session-list");
|
|
207
|
+
const summary = $("trash-summary");
|
|
208
|
+
const btnOld = $("btn-trash-empty-old");
|
|
209
|
+
const btnAll = $("btn-trash-empty-all");
|
|
210
|
+
const filterBar = $("trash-filter-bar");
|
|
184
211
|
if (!list) return;
|
|
185
212
|
|
|
186
213
|
const sessions = Trash.state.sessions;
|
|
@@ -196,9 +223,10 @@ const TrashView = (() => {
|
|
|
196
223
|
}
|
|
197
224
|
if (btnOld) btnOld.disabled = sessions.length === 0;
|
|
198
225
|
if (btnAll) btnAll.disabled = sessions.length === 0;
|
|
226
|
+
if (filterBar) filterBar.style.display = "";
|
|
199
227
|
|
|
200
228
|
if (sessions.length === 0) {
|
|
201
|
-
list.innerHTML = `<div class="
|
|
229
|
+
list.innerHTML = `<div class="trash-empty">${_t("trash.noSessionTrash")}</div>`;
|
|
202
230
|
return;
|
|
203
231
|
}
|
|
204
232
|
|
|
@@ -385,6 +413,15 @@ const TrashView = (() => {
|
|
|
385
413
|
if (btnAll) btnAll.addEventListener("click",
|
|
386
414
|
() => _emptyBulk(0, _activeTab === "session-trash"
|
|
387
415
|
? "trash.confirmEmptySessionAll" : "trash.confirmEmptyAll"));
|
|
416
|
+
|
|
417
|
+
// Filter chips
|
|
418
|
+
document.querySelectorAll(".trash-filter-chip").forEach(chip => {
|
|
419
|
+
chip.addEventListener("click", () => {
|
|
420
|
+
const val = chip.dataset.filter;
|
|
421
|
+
Trash.setFilterSource(val);
|
|
422
|
+
document.querySelectorAll(".trash-filter-chip").forEach(c => c.classList.toggle("active", c.dataset.filter === val));
|
|
423
|
+
});
|
|
424
|
+
});
|
|
388
425
|
}
|
|
389
426
|
|
|
390
427
|
function _subscribe() {
|
|
@@ -392,11 +429,11 @@ const TrashView = (() => {
|
|
|
392
429
|
Trash.on("trash:sessionsChanged", _renderSessions);
|
|
393
430
|
Trash.on("trash:filesError", (e) => {
|
|
394
431
|
const list = $("trash-list");
|
|
395
|
-
if (list) list.innerHTML = `<div class="
|
|
432
|
+
if (list) list.innerHTML = `<div class="trash-empty trash-error">${escapeHtml(e.message)}</div>`;
|
|
396
433
|
});
|
|
397
434
|
Trash.on("trash:sessionsError", (e) => {
|
|
398
435
|
const list = $("trash-session-list");
|
|
399
|
-
if (list) list.innerHTML = `<div class="
|
|
436
|
+
if (list) list.innerHTML = `<div class="trash-empty trash-error">${escapeHtml(e.message)}</div>`;
|
|
400
437
|
});
|
|
401
438
|
}
|
|
402
439
|
|
|
@@ -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;
|