claudeck 1.4.0 → 1.4.2
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/README.md +6 -8
- package/package.json +1 -1
- package/plugins/claude-editor/manifest.json +10 -0
- package/plugins/repos/manifest.json +10 -0
- package/public/css/core/theme.css +6 -21
- package/public/css/core/variables.css +2 -0
- package/public/css/features/message-queue.css +348 -0
- package/public/css/ui/commands.css +4 -4
- package/public/css/ui/messages.css +310 -78
- package/public/css/ui/right-panel.css +207 -0
- package/public/css/ui/sessions.css +173 -0
- package/public/css/ui/settings.css +75 -0
- package/public/index.html +10 -2
- package/public/js/components/add-project-modal.js +14 -0
- package/public/js/components/jump-to-latest.js +42 -0
- package/public/js/components/queue-stop-modal.js +23 -0
- package/public/js/components/settings-modal.js +65 -0
- package/public/js/core/api.js +15 -43
- package/public/js/core/dom.js +17 -0
- package/public/js/core/events.js +11 -0
- package/public/js/core/plugin-loader.js +96 -11
- package/public/js/core/store.js +11 -0
- package/public/js/core/utils.js +38 -2
- package/public/js/features/chat.js +49 -1
- package/public/js/features/message-queue.js +423 -0
- package/public/js/features/projects.js +185 -3
- package/public/js/main.js +4 -1
- package/public/js/panels/assistant-bot.js +16 -0
- package/public/js/panels/dev-docs.js +2 -2
- package/public/js/panels/memory.js +1 -0
- package/public/js/ui/context-gauge.js +10 -1
- package/public/js/ui/formatting.js +65 -11
- package/public/js/ui/header-dropdowns.js +30 -0
- package/public/js/ui/input-meta.js +13 -6
- package/public/js/ui/max-turns.js +6 -3
- package/public/js/ui/messages.js +97 -1
- package/public/js/ui/model-selector.js +1 -0
- package/public/js/ui/parallel.js +32 -2
- package/public/js/ui/permissions.js +1 -0
- package/public/js/ui/right-panel.js +0 -8
- package/public/js/ui/tab-sdk.js +395 -176
- package/public/style.css +2 -0
- package/server/memory-optimizer.js +17 -13
- package/server/routes/marketplace.js +316 -0
- package/server/routes/projects.js +0 -0
- package/server/ws-handler.js +22 -15
- package/server.js +18 -0
- package/plugins/event-stream/client.css +0 -207
- package/plugins/event-stream/client.js +0 -271
- package/plugins/linear/client.css +0 -345
- package/plugins/linear/client.js +0 -380
- package/plugins/linear/config.json +0 -5
- package/plugins/linear/server.js +0 -312
- package/plugins/sudoku/client.css +0 -196
- package/plugins/sudoku/client.js +0 -329
- package/plugins/tasks/client.css +0 -414
- package/plugins/tasks/client.js +0 -394
- package/plugins/tasks/server.js +0 -116
- package/plugins/tic-tac-toe/client.css +0 -167
- package/plugins/tic-tac-toe/client.js +0 -241
- package/public/js/components/linear-create-modal.js +0 -43
package/plugins/tasks/client.js
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
// Tasks Tab — Tab SDK plugin for Todo list with brag tracking
|
|
2
|
-
import { registerTab } from '/js/ui/tab-sdk.js';
|
|
3
|
-
|
|
4
|
-
const ICONS = {
|
|
5
|
-
star: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`,
|
|
6
|
-
archive: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>`,
|
|
7
|
-
unarchive: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>`,
|
|
8
|
-
starBrag: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`,
|
|
9
|
-
archiveBig: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>`,
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
function escapeHtml(str) {
|
|
13
|
-
const div = document.createElement('div');
|
|
14
|
-
div.textContent = str;
|
|
15
|
-
return div.innerHTML;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
registerTab({
|
|
19
|
-
id: 'tasks',
|
|
20
|
-
title: 'Tasks',
|
|
21
|
-
icon: '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
|
|
22
|
-
init(ctx) {
|
|
23
|
-
let todos = [];
|
|
24
|
-
let brags = [];
|
|
25
|
-
let showArchived = false;
|
|
26
|
-
let showBrags = false;
|
|
27
|
-
|
|
28
|
-
const PRIORITY_LABELS = ['none', 'low', 'medium', 'high'];
|
|
29
|
-
|
|
30
|
-
// ── Build DOM ────────────────────────────────────
|
|
31
|
-
const root = document.createElement('div');
|
|
32
|
-
root.className = 'tasks-tab';
|
|
33
|
-
root.style.cssText = 'display:flex;flex-direction:column;flex:1;overflow:hidden;';
|
|
34
|
-
|
|
35
|
-
root.innerHTML = `
|
|
36
|
-
<div class="todo-panel-header">
|
|
37
|
-
<h3>Todo</h3>
|
|
38
|
-
<div class="todo-header-actions">
|
|
39
|
-
<button class="todo-brag-toggle todo-toggle-btn" title="Show brag list">${ICONS.starBrag}</button>
|
|
40
|
-
<button class="todo-archive-toggle todo-toggle-btn" title="Show archived">${ICONS.archiveBig}</button>
|
|
41
|
-
<button class="todo-add-btn" title="Add todo">+</button>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
<div class="todo-list"></div>
|
|
45
|
-
<div class="todo-input-bar" style="display:none;">
|
|
46
|
-
<input type="text" class="todo-input" placeholder="New todo..." autocomplete="off">
|
|
47
|
-
</div>
|
|
48
|
-
`;
|
|
49
|
-
|
|
50
|
-
const todoList = root.querySelector('.todo-list');
|
|
51
|
-
const todoAddBtn = root.querySelector('.todo-add-btn');
|
|
52
|
-
const todoInputBar = root.querySelector('.todo-input-bar');
|
|
53
|
-
const todoInput = root.querySelector('.todo-input');
|
|
54
|
-
const archToggle = root.querySelector('.todo-archive-toggle');
|
|
55
|
-
const bragToggle = root.querySelector('.todo-brag-toggle');
|
|
56
|
-
const todoHeader = root.querySelector('.todo-panel-header h3');
|
|
57
|
-
|
|
58
|
-
// ── Render ───────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
function renderTodos() {
|
|
61
|
-
if (showBrags) {
|
|
62
|
-
renderBrags();
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!todos.length) {
|
|
67
|
-
const isArchived = showArchived;
|
|
68
|
-
todoList.innerHTML = `
|
|
69
|
-
<div class="todo-empty">
|
|
70
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
71
|
-
${isArchived
|
|
72
|
-
? '<polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/>'
|
|
73
|
-
: '<path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>'}
|
|
74
|
-
</svg>
|
|
75
|
-
<span>${isArchived ? 'No archived todos' : 'No todos yet'}</span>
|
|
76
|
-
${!isArchived ? '<span class="todo-empty-hint">Click + to add one</span>' : ''}
|
|
77
|
-
</div>`;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
todoList.innerHTML = '';
|
|
82
|
-
for (const t of todos) {
|
|
83
|
-
const pri = t.priority || 0;
|
|
84
|
-
const row = document.createElement('div');
|
|
85
|
-
row.className = 'todo-item' + (t.done ? ' done' : '');
|
|
86
|
-
if (pri > 0) row.classList.add(`priority-${pri}`);
|
|
87
|
-
row.dataset.id = t.id;
|
|
88
|
-
|
|
89
|
-
const priDot = document.createElement('button');
|
|
90
|
-
priDot.className = `todo-priority-dot priority-${pri}`;
|
|
91
|
-
priDot.title = `Priority: ${PRIORITY_LABELS[pri]} (click to change)`;
|
|
92
|
-
priDot.addEventListener('click', () => handlePriority(t.id, (pri + 1) % 4));
|
|
93
|
-
|
|
94
|
-
const cb = document.createElement('input');
|
|
95
|
-
cb.type = 'checkbox';
|
|
96
|
-
cb.checked = !!t.done;
|
|
97
|
-
cb.addEventListener('change', () => handleToggle(t.id, cb.checked ? 1 : 0));
|
|
98
|
-
|
|
99
|
-
const span = document.createElement('span');
|
|
100
|
-
span.className = 'todo-text';
|
|
101
|
-
span.textContent = t.text;
|
|
102
|
-
if (!showArchived) span.addEventListener('dblclick', () => startEdit(span, t));
|
|
103
|
-
|
|
104
|
-
const actions = document.createElement('span');
|
|
105
|
-
actions.className = 'todo-actions';
|
|
106
|
-
|
|
107
|
-
if (!showArchived) {
|
|
108
|
-
const bragBtn = document.createElement('button');
|
|
109
|
-
bragBtn.className = 'todo-action-btn todo-brag-btn';
|
|
110
|
-
bragBtn.title = 'Brag about this';
|
|
111
|
-
bragBtn.innerHTML = ICONS.star;
|
|
112
|
-
bragBtn.addEventListener('click', () => showBragPrompt(t));
|
|
113
|
-
actions.appendChild(bragBtn);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const archBtn = document.createElement('button');
|
|
117
|
-
archBtn.className = 'todo-action-btn todo-archive-btn';
|
|
118
|
-
archBtn.title = showArchived ? 'Unarchive' : 'Archive';
|
|
119
|
-
archBtn.innerHTML = showArchived ? ICONS.unarchive : ICONS.archive;
|
|
120
|
-
archBtn.addEventListener('click', () => handleArchive(t.id, !showArchived));
|
|
121
|
-
|
|
122
|
-
const del = document.createElement('button');
|
|
123
|
-
del.className = 'todo-action-btn todo-delete-btn';
|
|
124
|
-
del.textContent = '\u00d7';
|
|
125
|
-
del.title = 'Delete';
|
|
126
|
-
del.addEventListener('click', () => handleDelete(t.id));
|
|
127
|
-
|
|
128
|
-
actions.append(archBtn, del);
|
|
129
|
-
row.append(priDot, cb, span, actions);
|
|
130
|
-
todoList.appendChild(row);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function renderBrags() {
|
|
135
|
-
if (!brags.length) {
|
|
136
|
-
todoList.innerHTML = `
|
|
137
|
-
<div class="todo-empty">
|
|
138
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
139
|
-
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
|
140
|
-
</svg>
|
|
141
|
-
<span>No brags yet</span>
|
|
142
|
-
<span class="todo-empty-hint">Complete a todo and brag about it</span>
|
|
143
|
-
</div>`;
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
todoList.innerHTML = '';
|
|
147
|
-
for (const b of brags) {
|
|
148
|
-
const row = document.createElement('div');
|
|
149
|
-
row.className = 'brag-item';
|
|
150
|
-
|
|
151
|
-
const text = document.createElement('div');
|
|
152
|
-
text.className = 'brag-text';
|
|
153
|
-
text.textContent = b.text;
|
|
154
|
-
|
|
155
|
-
const summary = document.createElement('div');
|
|
156
|
-
summary.className = 'brag-summary';
|
|
157
|
-
summary.textContent = b.summary;
|
|
158
|
-
|
|
159
|
-
const date = document.createElement('div');
|
|
160
|
-
date.className = 'brag-date';
|
|
161
|
-
date.textContent = new Date(b.created_at * 1000).toLocaleDateString();
|
|
162
|
-
|
|
163
|
-
const del = document.createElement('button');
|
|
164
|
-
del.className = 'todo-action-btn todo-delete-btn brag-delete';
|
|
165
|
-
del.textContent = '\u00d7';
|
|
166
|
-
del.title = 'Delete';
|
|
167
|
-
del.addEventListener('click', async () => {
|
|
168
|
-
try {
|
|
169
|
-
await ctx.api.deleteBragApi(b.id);
|
|
170
|
-
brags = brags.filter(x => x.id !== b.id);
|
|
171
|
-
renderTodos();
|
|
172
|
-
refreshCounts();
|
|
173
|
-
} catch { /* ignore */ }
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
row.append(text, summary, date, del);
|
|
177
|
-
todoList.appendChild(row);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function updateHeaderToggle() {
|
|
182
|
-
archToggle.classList.toggle('active', showArchived);
|
|
183
|
-
archToggle.title = showArchived ? 'Show active todos' : 'Show archived';
|
|
184
|
-
bragToggle.classList.toggle('active', showBrags);
|
|
185
|
-
bragToggle.title = showBrags ? 'Show active todos' : 'Show brag list';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async function refreshCounts() {
|
|
189
|
-
try {
|
|
190
|
-
const counts = await ctx.api.fetchTodoCounts();
|
|
191
|
-
const label = showBrags ? 'Brags' : showArchived ? 'Archived' : 'Todo';
|
|
192
|
-
const count = showBrags ? counts.brags : showArchived ? counts.archived : counts.active;
|
|
193
|
-
todoHeader.textContent = `${label} (${count})`;
|
|
194
|
-
setBadge(archToggle, counts.archived);
|
|
195
|
-
setBadge(bragToggle, counts.brags);
|
|
196
|
-
} catch { /* ignore */ }
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function setBadge(btn, count) {
|
|
200
|
-
let badge = btn.querySelector('.todo-count-badge');
|
|
201
|
-
if (count > 0) {
|
|
202
|
-
if (!badge) {
|
|
203
|
-
badge = document.createElement('span');
|
|
204
|
-
badge.className = 'todo-count-badge';
|
|
205
|
-
btn.appendChild(badge);
|
|
206
|
-
}
|
|
207
|
-
badge.textContent = count;
|
|
208
|
-
} else if (badge) {
|
|
209
|
-
badge.remove();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ── CRUD handlers ────────────────────────────────
|
|
214
|
-
async function loadTodos() {
|
|
215
|
-
try {
|
|
216
|
-
todos = await ctx.api.fetchTodos(showArchived);
|
|
217
|
-
renderTodos();
|
|
218
|
-
refreshCounts();
|
|
219
|
-
} catch { /* ignore */ }
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async function handleToggle(id, done) {
|
|
223
|
-
try {
|
|
224
|
-
await ctx.api.updateTodoApi(id, { done });
|
|
225
|
-
const t = todos.find(x => x.id === id);
|
|
226
|
-
if (t) t.done = done;
|
|
227
|
-
renderTodos();
|
|
228
|
-
} catch { /* ignore */ }
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function handleArchive(id, archived) {
|
|
232
|
-
try {
|
|
233
|
-
await ctx.api.archiveTodoApi(id, archived);
|
|
234
|
-
todos = todos.filter(x => x.id !== id);
|
|
235
|
-
renderTodos();
|
|
236
|
-
refreshCounts();
|
|
237
|
-
} catch { /* ignore */ }
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async function handlePriority(id, priority) {
|
|
241
|
-
try {
|
|
242
|
-
await ctx.api.updateTodoApi(id, { priority });
|
|
243
|
-
const t = todos.find(x => x.id === id);
|
|
244
|
-
if (t) t.priority = priority;
|
|
245
|
-
renderTodos();
|
|
246
|
-
} catch { /* ignore */ }
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function handleDelete(id) {
|
|
250
|
-
try {
|
|
251
|
-
await ctx.api.deleteTodoApi(id);
|
|
252
|
-
todos = todos.filter(x => x.id !== id);
|
|
253
|
-
renderTodos();
|
|
254
|
-
refreshCounts();
|
|
255
|
-
} catch { /* ignore */ }
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function showBragPrompt(todo) {
|
|
259
|
-
const existing = document.querySelector('.brag-prompt-overlay');
|
|
260
|
-
if (existing) existing.remove();
|
|
261
|
-
|
|
262
|
-
const overlay = document.createElement('div');
|
|
263
|
-
overlay.className = 'brag-prompt-overlay';
|
|
264
|
-
overlay.innerHTML = `
|
|
265
|
-
<div class="brag-prompt">
|
|
266
|
-
<div class="brag-prompt-title">Brag about it!</div>
|
|
267
|
-
<div class="brag-prompt-task">${escapeHtml(todo.text)}</div>
|
|
268
|
-
<textarea class="brag-prompt-input" placeholder="Write a summary of what you accomplished..." maxlength="500" rows="4"></textarea>
|
|
269
|
-
<div class="brag-prompt-counter"><span class="brag-char-count">0</span>/500</div>
|
|
270
|
-
<div class="brag-prompt-actions">
|
|
271
|
-
<button class="brag-prompt-cancel">Cancel</button>
|
|
272
|
-
<button class="brag-prompt-submit">Brag it!</button>
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
`;
|
|
276
|
-
|
|
277
|
-
const textarea = overlay.querySelector('.brag-prompt-input');
|
|
278
|
-
const counter = overlay.querySelector('.brag-char-count');
|
|
279
|
-
const submitBtn = overlay.querySelector('.brag-prompt-submit');
|
|
280
|
-
const cancelBtn = overlay.querySelector('.brag-prompt-cancel');
|
|
281
|
-
|
|
282
|
-
textarea.addEventListener('input', () => { counter.textContent = textarea.value.length; });
|
|
283
|
-
cancelBtn.addEventListener('click', () => overlay.remove());
|
|
284
|
-
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
|
285
|
-
|
|
286
|
-
submitBtn.addEventListener('click', async () => {
|
|
287
|
-
const summary = textarea.value.trim();
|
|
288
|
-
if (!summary) { textarea.focus(); return; }
|
|
289
|
-
submitBtn.disabled = true;
|
|
290
|
-
submitBtn.textContent = 'Saving...';
|
|
291
|
-
try {
|
|
292
|
-
await ctx.api.bragTodoApi(todo.id, summary);
|
|
293
|
-
overlay.remove();
|
|
294
|
-
todos = todos.filter(x => x.id !== todo.id);
|
|
295
|
-
renderTodos();
|
|
296
|
-
refreshCounts();
|
|
297
|
-
} catch {
|
|
298
|
-
submitBtn.disabled = false;
|
|
299
|
-
submitBtn.textContent = 'Brag it!';
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
document.body.appendChild(overlay);
|
|
304
|
-
textarea.focus();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function startEdit(span, todo) {
|
|
308
|
-
span.contentEditable = 'true';
|
|
309
|
-
span.classList.add('editing');
|
|
310
|
-
span.focus();
|
|
311
|
-
|
|
312
|
-
const range = document.createRange();
|
|
313
|
-
range.selectNodeContents(span);
|
|
314
|
-
const sel = window.getSelection();
|
|
315
|
-
sel.removeAllRanges();
|
|
316
|
-
sel.addRange(range);
|
|
317
|
-
|
|
318
|
-
const finish = async () => {
|
|
319
|
-
span.contentEditable = 'false';
|
|
320
|
-
span.classList.remove('editing');
|
|
321
|
-
const newText = span.textContent.trim();
|
|
322
|
-
if (newText && newText !== todo.text) {
|
|
323
|
-
try {
|
|
324
|
-
await ctx.api.updateTodoApi(todo.id, { text: newText });
|
|
325
|
-
todo.text = newText;
|
|
326
|
-
} catch {
|
|
327
|
-
span.textContent = todo.text;
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
span.textContent = todo.text;
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
span.addEventListener('blur', finish, { once: true });
|
|
335
|
-
span.addEventListener('keydown', (e) => {
|
|
336
|
-
if (e.key === 'Enter') { e.preventDefault(); span.blur(); }
|
|
337
|
-
if (e.key === 'Escape') { span.textContent = todo.text; span.blur(); }
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// ── Event listeners ──────────────────────────────
|
|
342
|
-
todoAddBtn.addEventListener('click', () => {
|
|
343
|
-
todoInputBar.style.display = '';
|
|
344
|
-
todoInput.value = '';
|
|
345
|
-
todoInput.focus();
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
todoInput.addEventListener('keydown', (e) => {
|
|
349
|
-
if (e.key === 'Enter') {
|
|
350
|
-
const text = todoInput.value.trim();
|
|
351
|
-
if (!text) return;
|
|
352
|
-
todoInput.value = '';
|
|
353
|
-
todoInputBar.style.display = 'none';
|
|
354
|
-
ctx.api.createTodoApi(text).then(() => loadTodos()).catch(() => {});
|
|
355
|
-
}
|
|
356
|
-
if (e.key === 'Escape') {
|
|
357
|
-
todoInputBar.style.display = 'none';
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
archToggle.addEventListener('click', () => {
|
|
362
|
-
showArchived = !showArchived;
|
|
363
|
-
showBrags = false;
|
|
364
|
-
updateHeaderToggle();
|
|
365
|
-
todoAddBtn.style.display = showArchived ? 'none' : '';
|
|
366
|
-
todoInputBar.style.display = 'none';
|
|
367
|
-
loadTodos();
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
bragToggle.addEventListener('click', async () => {
|
|
371
|
-
showBrags = !showBrags;
|
|
372
|
-
showArchived = false;
|
|
373
|
-
updateHeaderToggle();
|
|
374
|
-
todoAddBtn.style.display = showBrags ? 'none' : '';
|
|
375
|
-
todoInputBar.style.display = 'none';
|
|
376
|
-
if (showBrags) {
|
|
377
|
-
try { brags = await ctx.api.fetchBrags(); } catch { brags = []; }
|
|
378
|
-
renderTodos();
|
|
379
|
-
refreshCounts();
|
|
380
|
-
} else {
|
|
381
|
-
loadTodos();
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
// Initial load
|
|
386
|
-
loadTodos();
|
|
387
|
-
|
|
388
|
-
return root;
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
onActivate() {
|
|
392
|
-
// no-op: todos load on init
|
|
393
|
-
},
|
|
394
|
-
});
|
package/plugins/tasks/server.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import { listTodos, createTodo, updateTodo, archiveTodo, deleteTodo, createBrag, listBrags, deleteBrag, getTodoCounts } from "../../db.js";
|
|
3
|
-
|
|
4
|
-
const router = Router();
|
|
5
|
-
|
|
6
|
-
router.get("/", async (req, res) => {
|
|
7
|
-
try {
|
|
8
|
-
const archived = req.query.archived === "1";
|
|
9
|
-
const todos = await listTodos(archived);
|
|
10
|
-
res.json(todos);
|
|
11
|
-
} catch (err) {
|
|
12
|
-
res.status(500).json({ error: err.message });
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
router.get("/counts", async (req, res) => {
|
|
17
|
-
try {
|
|
18
|
-
res.json(await getTodoCounts());
|
|
19
|
-
} catch (err) {
|
|
20
|
-
res.status(500).json({ error: err.message });
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
router.post("/", async (req, res) => {
|
|
25
|
-
try {
|
|
26
|
-
const { text } = req.body;
|
|
27
|
-
if (!text || typeof text !== "string") {
|
|
28
|
-
return res.status(400).json({ error: "text is required" });
|
|
29
|
-
}
|
|
30
|
-
const info = await createTodo(text.trim());
|
|
31
|
-
res.json({ id: info.lastInsertRowid });
|
|
32
|
-
} catch (err) {
|
|
33
|
-
res.status(500).json({ error: err.message });
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
router.put("/:id", async (req, res) => {
|
|
38
|
-
try {
|
|
39
|
-
const id = Number(req.params.id);
|
|
40
|
-
const { text, done, priority } = req.body;
|
|
41
|
-
await updateTodo(id, text ?? null, done ?? null, priority ?? null);
|
|
42
|
-
res.json({ ok: true });
|
|
43
|
-
} catch (err) {
|
|
44
|
-
res.status(500).json({ error: err.message });
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
router.put("/:id/archive", async (req, res) => {
|
|
49
|
-
try {
|
|
50
|
-
const id = Number(req.params.id);
|
|
51
|
-
const { archived } = req.body;
|
|
52
|
-
await archiveTodo(id, archived ?? true);
|
|
53
|
-
res.json({ ok: true });
|
|
54
|
-
} catch (err) {
|
|
55
|
-
res.status(500).json({ error: err.message });
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
router.delete("/:id", async (req, res) => {
|
|
60
|
-
try {
|
|
61
|
-
const id = Number(req.params.id);
|
|
62
|
-
await deleteTodo(id);
|
|
63
|
-
res.json({ ok: true });
|
|
64
|
-
} catch (err) {
|
|
65
|
-
res.status(500).json({ error: err.message });
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// ── Brags ──────────────────────────────────────────────
|
|
70
|
-
router.get("/brags", async (req, res) => {
|
|
71
|
-
try {
|
|
72
|
-
res.json(await listBrags());
|
|
73
|
-
} catch (err) {
|
|
74
|
-
res.status(500).json({ error: err.message });
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
router.post("/:id/brag", async (req, res) => {
|
|
79
|
-
try {
|
|
80
|
-
const todoId = Number(req.params.id);
|
|
81
|
-
const { summary } = req.body;
|
|
82
|
-
if (!summary || typeof summary !== "string" || summary.trim().length === 0) {
|
|
83
|
-
return res.status(400).json({ error: "summary is required" });
|
|
84
|
-
}
|
|
85
|
-
if (summary.length > 500) {
|
|
86
|
-
return res.status(400).json({ error: "summary must be 500 chars or less" });
|
|
87
|
-
}
|
|
88
|
-
// Get the todo text before archiving
|
|
89
|
-
const todos = await listTodos(false);
|
|
90
|
-
const todo = todos.find(t => t.id === todoId);
|
|
91
|
-
const archivedTodos = await listTodos(true);
|
|
92
|
-
const archivedTodo = archivedTodos.find(t => t.id === todoId);
|
|
93
|
-
const foundTodo = todo || archivedTodo;
|
|
94
|
-
if (!foundTodo) {
|
|
95
|
-
return res.status(404).json({ error: "Todo not found" });
|
|
96
|
-
}
|
|
97
|
-
const info = await createBrag(todoId, foundTodo.text, summary.trim());
|
|
98
|
-
// Archive the todo
|
|
99
|
-
await archiveTodo(todoId, true);
|
|
100
|
-
res.json({ id: info.lastInsertRowid });
|
|
101
|
-
} catch (err) {
|
|
102
|
-
res.status(500).json({ error: err.message });
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
router.delete("/brags/:id", async (req, res) => {
|
|
107
|
-
try {
|
|
108
|
-
const id = Number(req.params.id);
|
|
109
|
-
await deleteBrag(id);
|
|
110
|
-
res.json({ ok: true });
|
|
111
|
-
} catch (err) {
|
|
112
|
-
res.status(500).json({ error: err.message });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
export default router;
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/* Tic Tac Toe — Tab SDK plugin styles */
|
|
2
|
-
|
|
3
|
-
.tic-tac-toe-tab {
|
|
4
|
-
font-family: var(--font-mono);
|
|
5
|
-
color: var(--text);
|
|
6
|
-
padding: 12px;
|
|
7
|
-
gap: 12px;
|
|
8
|
-
align-items: center;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/* ── Header ── */
|
|
12
|
-
.ttt-header {
|
|
13
|
-
display: flex;
|
|
14
|
-
justify-content: space-between;
|
|
15
|
-
align-items: center;
|
|
16
|
-
width: 100%;
|
|
17
|
-
max-width: 240px;
|
|
18
|
-
gap: 8px;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.ttt-diff-btns {
|
|
22
|
-
display: flex;
|
|
23
|
-
gap: 4px;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.ttt-diff-btn {
|
|
27
|
-
background: var(--bg-tertiary);
|
|
28
|
-
color: var(--text-secondary);
|
|
29
|
-
border: 1px solid var(--border);
|
|
30
|
-
border-radius: var(--radius);
|
|
31
|
-
padding: 3px 8px;
|
|
32
|
-
font-size: 11px;
|
|
33
|
-
font-family: var(--font-mono);
|
|
34
|
-
cursor: pointer;
|
|
35
|
-
transition: all 0.15s;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.ttt-diff-btn:hover {
|
|
39
|
-
background: var(--bg-secondary);
|
|
40
|
-
color: var(--text);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.ttt-diff-btn.active {
|
|
44
|
-
background: var(--accent);
|
|
45
|
-
color: var(--bg);
|
|
46
|
-
border-color: var(--accent);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/* ── Status ── */
|
|
50
|
-
.ttt-status {
|
|
51
|
-
font-size: 13px;
|
|
52
|
-
font-weight: 600;
|
|
53
|
-
text-align: center;
|
|
54
|
-
min-height: 20px;
|
|
55
|
-
color: var(--text-secondary);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.ttt-status.status-win {
|
|
59
|
-
color: var(--success);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.ttt-status.status-lose {
|
|
63
|
-
color: var(--error);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.ttt-status.status-draw {
|
|
67
|
-
color: var(--warning);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/* ── Board ── */
|
|
71
|
-
.ttt-board-wrap {
|
|
72
|
-
display: flex;
|
|
73
|
-
justify-content: center;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.ttt-grid {
|
|
77
|
-
display: grid;
|
|
78
|
-
grid-template-columns: repeat(3, 70px);
|
|
79
|
-
grid-template-rows: repeat(3, 70px);
|
|
80
|
-
gap: 4px;
|
|
81
|
-
background: var(--border);
|
|
82
|
-
border-radius: var(--radius);
|
|
83
|
-
padding: 4px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.ttt-cell {
|
|
87
|
-
display: flex;
|
|
88
|
-
align-items: center;
|
|
89
|
-
justify-content: center;
|
|
90
|
-
background: var(--bg-secondary);
|
|
91
|
-
border-radius: var(--radius);
|
|
92
|
-
font-size: 28px;
|
|
93
|
-
font-weight: 700;
|
|
94
|
-
cursor: default;
|
|
95
|
-
transition: all 0.15s;
|
|
96
|
-
user-select: none;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.ttt-cell.empty {
|
|
100
|
-
cursor: pointer;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.ttt-cell.empty:hover {
|
|
104
|
-
background: var(--bg-tertiary);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.ttt-cell.is-x {
|
|
108
|
-
color: var(--accent);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.ttt-cell.is-o {
|
|
112
|
-
color: var(--error);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.ttt-cell.winner {
|
|
116
|
-
background: rgba(99, 102, 241, 0.15);
|
|
117
|
-
animation: ttt-pulse 0.6s ease-in-out infinite alternate;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
@keyframes ttt-pulse {
|
|
121
|
-
from { transform: scale(1); }
|
|
122
|
-
to { transform: scale(1.08); }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/* ── Scores ── */
|
|
126
|
-
.ttt-scores {
|
|
127
|
-
display: flex;
|
|
128
|
-
gap: 8px;
|
|
129
|
-
align-items: center;
|
|
130
|
-
font-size: 12px;
|
|
131
|
-
color: var(--text-dim);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.ttt-score-item {
|
|
135
|
-
display: flex;
|
|
136
|
-
gap: 4px;
|
|
137
|
-
align-items: center;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.ttt-score-x { color: var(--accent); font-weight: 600; }
|
|
141
|
-
.ttt-score-o { color: var(--error); font-weight: 600; }
|
|
142
|
-
.ttt-score-draw { color: var(--warning); font-weight: 600; }
|
|
143
|
-
.ttt-score-sep { color: var(--border); }
|
|
144
|
-
|
|
145
|
-
/* ── Actions ── */
|
|
146
|
-
.ttt-actions {
|
|
147
|
-
display: flex;
|
|
148
|
-
gap: 6px;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.ttt-action-btn {
|
|
152
|
-
background: var(--bg-tertiary);
|
|
153
|
-
color: var(--text-secondary);
|
|
154
|
-
border: 1px solid var(--border);
|
|
155
|
-
border-radius: var(--radius);
|
|
156
|
-
padding: 4px 10px;
|
|
157
|
-
font-size: 11px;
|
|
158
|
-
font-family: var(--font-mono);
|
|
159
|
-
cursor: pointer;
|
|
160
|
-
transition: all 0.15s;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.ttt-action-btn:hover {
|
|
164
|
-
background: var(--bg-secondary);
|
|
165
|
-
color: var(--text);
|
|
166
|
-
border-color: var(--text-dim);
|
|
167
|
-
}
|