pawmode 1.2.0 → 1.4.0

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.
@@ -48,12 +48,13 @@ function generateDashboardHTML(theme, botName) {
48
48
  }
49
49
  };
50
50
  const t = themes[theme];
51
+ const safeBotName = botName.replace(/[&<>"']/g, "");
51
52
  return `<!DOCTYPE html>
52
53
  <html lang="en">
53
54
  <head>
54
55
  <meta charset="UTF-8">
55
56
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
56
- <title>${botName} — Task Dashboard</title>
57
+ <title>${safeBotName} — Task Dashboard</title>
57
58
  <link rel="preconnect" href="https://fonts.googleapis.com">
58
59
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
59
60
  <style>
@@ -113,168 +114,273 @@ header{display:flex;align-items:center;justify-content:space-between;padding:20p
113
114
  .form-actions .save{background:var(--accent);color:var(--bg);border-color:var(--accent);font-weight:600}
114
115
  .form-actions .save:hover{opacity:.9}
115
116
  .form-actions .cancel:hover{border-color:var(--text-dim)}
117
+ .toast{position:fixed;bottom:20px;right:20px;padding:10px 16px;border-radius:8px;font-size:12px;opacity:0;transition:opacity .3s;pointer-events:none;z-index:100}
118
+ .toast.show{opacity:1}
119
+ .toast.error{background:var(--high);color:#fff}
120
+ .toast.success{background:var(--done);color:#fff}
116
121
  </style>
117
122
  </head>
118
123
  <body>
119
124
  <header>
120
- <div class="logo"><span>&#x1F43E;</span> ${botName}</div>
125
+ <div class="logo"><span>&#x1F43E;</span> ${safeBotName}</div>
121
126
  <div class="theme-switcher">
122
127
  <div class="theme-dot${theme === "paw" ? " active" : ""}" data-theme="paw" title="Paw"></div>
123
128
  <div class="theme-dot${theme === "midnight" ? " active" : ""}" data-theme="midnight" title="Midnight"></div>
124
129
  <div class="theme-dot${theme === "neon" ? " active" : ""}" data-theme="neon" title="Neon"></div>
125
130
  </div>
126
131
  </header>
127
- <div class="board">
128
- <div class="column col-todo" data-status="todo">
129
- <div class="col-header"><span class="col-title">Todo</span><span class="col-count" id="count-todo">0</span></div>
130
- <div class="cards" id="cards-todo"></div>
131
- <button class="add-btn" onclick="showForm('todo')">+ Add task</button>
132
- <div class="add-form" id="form-todo">
133
- <input type="text" id="input-todo" placeholder="Task title..." onkeydown="if(event.key==='Enter')saveNew('todo')">
134
- <textarea id="desc-todo" placeholder="Description (optional)" rows="2"></textarea>
135
- <div class="form-actions">
136
- <button class="cancel" onclick="hideForm('todo')">Cancel</button>
137
- <button class="save" onclick="saveNew('todo')">Add</button>
138
- </div>
139
- </div>
140
- </div>
141
- <div class="column col-progress" data-status="in-progress">
142
- <div class="col-header"><span class="col-title">In Progress</span><span class="col-count" id="count-in-progress">0</span></div>
143
- <div class="cards" id="cards-in-progress"></div>
144
- <button class="add-btn" onclick="showForm('in-progress')">+ Add task</button>
145
- <div class="add-form" id="form-in-progress">
146
- <input type="text" id="input-in-progress" placeholder="Task title..." onkeydown="if(event.key==='Enter')saveNew('in-progress')">
147
- <textarea id="desc-in-progress" placeholder="Description (optional)" rows="2"></textarea>
148
- <div class="form-actions">
149
- <button class="cancel" onclick="hideForm('in-progress')">Cancel</button>
150
- <button class="save" onclick="saveNew('in-progress')">Add</button>
151
- </div>
152
- </div>
153
- </div>
154
- <div class="column col-done" data-status="done">
155
- <div class="col-header"><span class="col-title">Done</span><span class="col-count" id="count-done">0</span></div>
156
- <div class="cards" id="cards-done"></div>
157
- <button class="add-btn" onclick="showForm('done')">+ Add task</button>
158
- <div class="add-form" id="form-done">
159
- <input type="text" id="input-done" placeholder="Task title..." onkeydown="if(event.key==='Enter')saveNew('done')">
160
- <textarea id="desc-done" placeholder="Description (optional)" rows="2"></textarea>
161
- <div class="form-actions">
162
- <button class="cancel" onclick="hideForm('done')">Cancel</button>
163
- <button class="save" onclick="saveNew('done')">Add</button>
164
- </div>
165
- </div>
166
- </div>
167
- </div>
132
+ <div class="board" id="board"></div>
133
+ <div class="toast" id="toast"></div>
168
134
  <script>
169
- let tasks=[];
170
- let dragId=null;
135
+ var BOTNAME = "${safeBotName}";
136
+ var tasks = [];
137
+ var dragId = null;
171
138
 
172
- async function api(path,opts){
173
- const r=await fetch('/api/'+path,{headers:{'Content-Type':'application/json'},...opts});
174
- return r.json();
139
+ function toast(msg, type) {
140
+ var el = document.getElementById("toast");
141
+ el.textContent = msg;
142
+ el.className = "toast show " + (type || "success");
143
+ setTimeout(function() { el.className = "toast"; }, 2000);
175
144
  }
176
145
 
177
- async function load(){
178
- tasks=await api('tasks');
179
- render();
146
+ function api(path, opts) {
147
+ return fetch("/api/" + path, Object.assign({ headers: {"Content-Type": "application/json"} }, opts || {}))
148
+ .then(function(r) {
149
+ if (!r.ok) throw new Error("Request failed: " + r.status);
150
+ return r.json();
151
+ })
152
+ .catch(function(err) {
153
+ toast(err.message, "error");
154
+ throw err;
155
+ });
180
156
  }
181
157
 
182
- function render(){
183
- for(const s of['todo','in-progress','done']){
184
- const container=document.getElementById('cards-'+s);
185
- const filtered=tasks.filter(t=>t.status===s).sort((a,b)=>a.order-b.order);
186
- document.getElementById('count-'+s).textContent=filtered.length;
187
- if(filtered.length===0){
188
- container.innerHTML='<div class="empty">No tasks here yet.<br>${botName} is waiting for work! &#x1F43E;</div>';
189
- }else{
190
- container.innerHTML=filtered.map(t=>\`
191
- <div class="card" draggable="true" data-id="\${t.id}" ondragstart="onDragStart(event)" ondragend="onDragEnd(event)">
192
- <div class="card-title" contenteditable="true" onblur="updateTitle('\${t.id}',this.textContent)">\${esc(t.title)}</div>
193
- \${t.description?'<div class="card-desc" contenteditable="true" onblur="updateDesc(\\''+t.id+'\\',this.textContent)">'+esc(t.description)+'</div>':''}
194
- <div class="card-footer">
195
- <div class="priority">
196
- <div class="priority-dot high\${t.priority==='high'?' active':''}" onclick="setPriority('\${t.id}','high')" title="High"></div>
197
- <div class="priority-dot normal\${t.priority==='normal'?' active':''}" onclick="setPriority('\${t.id}','normal')" title="Normal"></div>
198
- <div class="priority-dot low\${t.priority==='low'?' active':''}" onclick="setPriority('\${t.id}','low')" title="Low"></div>
199
- </div>
200
- <span class="card-delete" onclick="del('\${t.id}')">&#x2715;</span>
201
- </div>
202
- </div>
203
- \`).join('');
204
- }
158
+ function load() {
159
+ api("tasks").then(function(data) {
160
+ tasks = data;
161
+ render();
162
+ });
205
163
  }
164
+
165
+ function esc(s) {
166
+ var d = document.createElement("div");
167
+ d.textContent = s;
168
+ return d.innerHTML;
206
169
  }
207
170
 
208
- function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
171
+ function render() {
172
+ var statuses = ["todo", "in-progress", "done"];
173
+ var labels = { "todo": "Todo", "in-progress": "In Progress", "done": "Done" };
174
+ var colClass = { "todo": "col-todo", "in-progress": "col-progress", "done": "col-done" };
175
+ var board = document.getElementById("board");
176
+ board.innerHTML = "";
209
177
 
210
- function showForm(s){document.getElementById('form-'+s).classList.add('show');document.getElementById('input-'+s).focus()}
211
- function hideForm(s){document.getElementById('form-'+s).classList.remove('show');document.getElementById('input-'+s).value='';document.getElementById('desc-'+s).value=''}
178
+ statuses.forEach(function(status) {
179
+ var filtered = tasks.filter(function(t) { return t.status === status; }).sort(function(a, b) { return a.order - b.order; });
212
180
 
213
- async function saveNew(status){
214
- const title=document.getElementById('input-'+status).value.trim();
215
- if(!title)return;
216
- const desc=document.getElementById('desc-'+status).value.trim();
217
- const task=await api('tasks',{method:'POST',body:JSON.stringify({title,description:desc||undefined,status,priority:'normal'})});
218
- tasks.push(task);
219
- render();
220
- hideForm(status);
221
- }
181
+ var col = document.createElement("div");
182
+ col.className = "column " + colClass[status];
183
+ col.setAttribute("data-status", status);
222
184
 
223
- async function updateTitle(id,title){
224
- title=title.trim();if(!title)return;
225
- await api('tasks/'+id,{method:'PUT',body:JSON.stringify({title})});
226
- const t=tasks.find(x=>x.id===id);if(t)t.title=title;
227
- }
185
+ // Header
186
+ var header = document.createElement("div");
187
+ header.className = "col-header";
188
+ header.innerHTML = '<span class="col-title">' + labels[status] + '</span><span class="col-count">' + filtered.length + '</span>';
189
+ col.appendChild(header);
228
190
 
229
- async function updateDesc(id,desc){
230
- desc=desc.trim();
231
- await api('tasks/'+id,{method:'PUT',body:JSON.stringify({description:desc||undefined})});
232
- const t=tasks.find(x=>x.id===id);if(t)t.description=desc||undefined;
233
- }
191
+ // Cards container
192
+ var cardsDiv = document.createElement("div");
193
+ cardsDiv.className = "cards";
234
194
 
235
- async function setPriority(id,priority){
236
- await api('tasks/'+id,{method:'PUT',body:JSON.stringify({priority})});
237
- const t=tasks.find(x=>x.id===id);if(t)t.priority=priority;
238
- render();
239
- }
195
+ if (filtered.length === 0) {
196
+ cardsDiv.innerHTML = '<div class="empty">No tasks here yet.<br>' + BOTNAME + ' is waiting for work! &#x1F43E;</div>';
197
+ } else {
198
+ filtered.forEach(function(task) {
199
+ var card = document.createElement("div");
200
+ card.className = "card";
201
+ card.draggable = true;
202
+ card.setAttribute("data-id", task.id);
240
203
 
241
- async function del(id){
242
- await api('tasks/'+id,{method:'DELETE'});
243
- tasks=tasks.filter(t=>t.id!==id);
244
- render();
245
- }
204
+ var titleDiv = document.createElement("div");
205
+ titleDiv.className = "card-title";
206
+ titleDiv.contentEditable = "true";
207
+ titleDiv.textContent = task.title;
208
+ titleDiv.addEventListener("blur", function() {
209
+ var newTitle = this.textContent.trim();
210
+ if (newTitle && newTitle !== task.title) {
211
+ api("tasks/" + task.id, { method: "PUT", body: JSON.stringify({ title: newTitle }) });
212
+ task.title = newTitle;
213
+ }
214
+ });
215
+ card.appendChild(titleDiv);
246
216
 
247
- function onDragStart(e){
248
- dragId=e.target.dataset.id;
249
- e.target.classList.add('dragging');
250
- e.dataTransfer.effectAllowed='move';
251
- }
217
+ if (task.description) {
218
+ var descDiv = document.createElement("div");
219
+ descDiv.className = "card-desc";
220
+ descDiv.contentEditable = "true";
221
+ descDiv.textContent = task.description;
222
+ descDiv.addEventListener("blur", function() {
223
+ var newDesc = this.textContent.trim();
224
+ api("tasks/" + task.id, { method: "PUT", body: JSON.stringify({ description: newDesc || undefined }) });
225
+ task.description = newDesc || undefined;
226
+ });
227
+ card.appendChild(descDiv);
228
+ }
252
229
 
253
- function onDragEnd(e){
254
- e.target.classList.remove('dragging');
255
- dragId=null;
256
- document.querySelectorAll('.column').forEach(c=>c.classList.remove('drag-over'));
257
- }
230
+ var footer = document.createElement("div");
231
+ footer.className = "card-footer";
258
232
 
259
- document.querySelectorAll('.column').forEach(col=>{
260
- col.addEventListener('dragover',e=>{e.preventDefault();e.dataTransfer.dropEffect='move';col.classList.add('drag-over')});
261
- col.addEventListener('dragleave',()=>col.classList.remove('drag-over'));
262
- col.addEventListener('drop',async e=>{
263
- e.preventDefault();col.classList.remove('drag-over');
264
- if(!dragId)return;
265
- const status=col.dataset.status;
266
- const order=tasks.filter(t=>t.status===status).length;
267
- await api('tasks/'+dragId,{method:'PUT',body:JSON.stringify({status,order})});
268
- const t=tasks.find(x=>x.id===dragId);
269
- if(t){t.status=status;t.order=order}
270
- render();
271
- });
272
- });
233
+ var priorityDiv = document.createElement("div");
234
+ priorityDiv.className = "priority";
235
+ ["high", "normal", "low"].forEach(function(p) {
236
+ var dot = document.createElement("div");
237
+ dot.className = "priority-dot " + p + (task.priority === p ? " active" : "");
238
+ dot.title = p.charAt(0).toUpperCase() + p.slice(1);
239
+ dot.addEventListener("click", function(e) {
240
+ e.stopPropagation();
241
+ api("tasks/" + task.id, { method: "PUT", body: JSON.stringify({ priority: p }) }).then(function() {
242
+ task.priority = p;
243
+ render();
244
+ });
245
+ });
246
+ priorityDiv.appendChild(dot);
247
+ });
248
+ footer.appendChild(priorityDiv);
273
249
 
274
- document.querySelectorAll('.theme-dot').forEach(dot=>{
275
- dot.addEventListener('click',()=>{
276
- window.location.href='/?theme='+dot.dataset.theme;
277
- });
250
+ var delBtn = document.createElement("span");
251
+ delBtn.className = "card-delete";
252
+ delBtn.innerHTML = "&#x2715;";
253
+ delBtn.addEventListener("click", function(e) {
254
+ e.stopPropagation();
255
+ api("tasks/" + task.id, { method: "DELETE" }).then(function() {
256
+ tasks = tasks.filter(function(t) { return t.id !== task.id; });
257
+ render();
258
+ });
259
+ });
260
+ footer.appendChild(delBtn);
261
+ card.appendChild(footer);
262
+
263
+ // Drag events
264
+ card.addEventListener("dragstart", function(e) {
265
+ dragId = task.id;
266
+ this.classList.add("dragging");
267
+ e.dataTransfer.effectAllowed = "move";
268
+ });
269
+ card.addEventListener("dragend", function() {
270
+ this.classList.remove("dragging");
271
+ dragId = null;
272
+ document.querySelectorAll(".column").forEach(function(c) { c.classList.remove("drag-over"); });
273
+ });
274
+
275
+ cardsDiv.appendChild(card);
276
+ });
277
+ }
278
+ col.appendChild(cardsDiv);
279
+
280
+ // Add button
281
+ var addBtn = document.createElement("button");
282
+ addBtn.type = "button";
283
+ addBtn.className = "add-btn";
284
+ addBtn.textContent = "+ Add task";
285
+ addBtn.addEventListener("click", function() {
286
+ formDiv.classList.add("show");
287
+ titleInput.focus();
288
+ });
289
+ col.appendChild(addBtn);
290
+
291
+ // Add form
292
+ var formDiv = document.createElement("div");
293
+ formDiv.className = "add-form";
294
+
295
+ var titleInput = document.createElement("input");
296
+ titleInput.type = "text";
297
+ titleInput.placeholder = "Task title...";
298
+ titleInput.addEventListener("keydown", function(e) {
299
+ if (e.key === "Enter") doSave();
300
+ });
301
+ formDiv.appendChild(titleInput);
302
+
303
+ var descInput = document.createElement("textarea");
304
+ descInput.placeholder = "Description (optional)";
305
+ descInput.rows = 2;
306
+ formDiv.appendChild(descInput);
307
+
308
+ var actions = document.createElement("div");
309
+ actions.className = "form-actions";
310
+
311
+ var cancelBtn = document.createElement("button");
312
+ cancelBtn.type = "button";
313
+ cancelBtn.className = "cancel";
314
+ cancelBtn.textContent = "Cancel";
315
+ cancelBtn.addEventListener("click", function() {
316
+ formDiv.classList.remove("show");
317
+ titleInput.value = "";
318
+ descInput.value = "";
319
+ });
320
+ actions.appendChild(cancelBtn);
321
+
322
+ var saveBtn = document.createElement("button");
323
+ saveBtn.type = "button";
324
+ saveBtn.className = "save";
325
+ saveBtn.textContent = "Add";
326
+
327
+ function doSave() {
328
+ var title = titleInput.value.trim();
329
+ if (!title) return;
330
+ var desc = descInput.value.trim();
331
+ saveBtn.disabled = true;
332
+ saveBtn.textContent = "...";
333
+ var body = { title: title, status: status, priority: "normal" };
334
+ if (desc) body.description = desc;
335
+ api("tasks", { method: "POST", body: JSON.stringify(body) })
336
+ .then(function(task) {
337
+ tasks.push(task);
338
+ render();
339
+ toast("Task added!");
340
+ })
341
+ .catch(function() {
342
+ saveBtn.disabled = false;
343
+ saveBtn.textContent = "Add";
344
+ });
345
+ }
346
+
347
+ saveBtn.addEventListener("click", doSave);
348
+ actions.appendChild(saveBtn);
349
+ formDiv.appendChild(actions);
350
+ col.appendChild(formDiv);
351
+
352
+ // Drop events on column
353
+ col.addEventListener("dragover", function(e) {
354
+ e.preventDefault();
355
+ e.dataTransfer.dropEffect = "move";
356
+ this.classList.add("drag-over");
357
+ });
358
+ col.addEventListener("dragleave", function() {
359
+ this.classList.remove("drag-over");
360
+ });
361
+ col.addEventListener("drop", function(e) {
362
+ e.preventDefault();
363
+ this.classList.remove("drag-over");
364
+ if (!dragId) return;
365
+ var newStatus = this.getAttribute("data-status");
366
+ var order = tasks.filter(function(t) { return t.status === newStatus; }).length;
367
+ api("tasks/" + dragId, { method: "PUT", body: JSON.stringify({ status: newStatus, order: order }) })
368
+ .then(function() {
369
+ var task = tasks.find(function(t) { return t.id === dragId; });
370
+ if (task) { task.status = newStatus; task.order = order; }
371
+ render();
372
+ });
373
+ });
374
+
375
+ board.appendChild(col);
376
+ });
377
+ }
378
+
379
+ // Theme switcher
380
+ document.querySelectorAll(".theme-dot").forEach(function(dot) {
381
+ dot.addEventListener("click", function() {
382
+ window.location.href = "/?theme=" + this.getAttribute("data-theme");
383
+ });
278
384
  });
279
385
 
280
386
  load();
@@ -1,3 +1,3 @@
1
- import { CONFIG_FILE, readConfig, startDashboard, writeConfig } from "./dashboard-server--wwlA0Pa.js";
1
+ import { CONFIG_FILE, readConfig, startDashboard, writeConfig } from "./dashboard-server-BAyeozOa.js";
2
2
 
3
3
  export { startDashboard };