Merge branch 'dev'
CI/CD / deploy (push) Failing after 2m4s Details

This commit is contained in:
Jeff Emmett 2026-04-06 17:21:59 -04:00
commit 8018acac2c
1 changed files with 89 additions and 7 deletions

View File

@ -45,6 +45,10 @@ class FolkTasksBoard extends HTMLElement {
private _showColumnEditor = false; private _showColumnEditor = false;
// Board labels (from server) // Board labels (from server)
private _boardLabels: string[] = []; private _boardLabels: string[] = [];
// Inline workspace creation
private _showCreateWs = false;
// Inline delete confirmation
private _confirmDeleteId: string | null = null;
// ClickUp integration state // ClickUp integration state
private _cuConnected = false; private _cuConnected = false;
private _cuTeamName = ''; private _cuTeamName = '';
@ -434,12 +438,12 @@ class FolkTasksBoard extends HTMLElement {
return ''; return '';
} }
private async createWorkspace() { private async createWorkspace(name: string) {
const name = prompt("Workspace name:");
if (!name?.trim()) return; if (!name?.trim()) return;
if (this.isDemo) { if (this.isDemo) {
const slug = name.trim().toLowerCase().replace(/\s+/g, "-"); const slug = name.trim().toLowerCase().replace(/\s+/g, "-");
this.workspaces.push({ slug, name: name.trim(), icon: "\u{1F4CB}", task_count: 0, member_count: 1 }); this.workspaces.push({ slug, name: name.trim(), icon: "\u{1F4CB}", task_count: 0, member_count: 1 });
this._showCreateWs = false;
this.render(); this.render();
return; return;
} }
@ -450,7 +454,14 @@ class FolkTasksBoard extends HTMLElement {
headers: this.authHeaders({ "Content-Type": "application/json" }), headers: this.authHeaders({ "Content-Type": "application/json" }),
body: JSON.stringify({ name: name.trim() }), body: JSON.stringify({ name: name.trim() }),
}); });
if (res.ok) this.loadWorkspaces(); if (res.ok) {
this._showCreateWs = false;
this.loadWorkspaces();
} else {
const data = await res.json().catch(() => ({}));
this.error = data.error || `Failed to create workspace (${res.status})`;
this.render();
}
} catch { this.error = "Failed to create workspace"; this.render(); } } catch { this.error = "Failed to create workspace"; this.render(); }
} }
@ -494,7 +505,18 @@ class FolkTasksBoard extends HTMLElement {
} catch { this.error = "Failed to update task"; this.render(); } } catch { this.error = "Failed to update task"; this.render(); }
} }
private confirmDelete(taskId: string) {
this._confirmDeleteId = taskId;
this.render();
}
private cancelDelete() {
this._confirmDeleteId = null;
this.render();
}
private async deleteTask(taskId: string) { private async deleteTask(taskId: string) {
this._confirmDeleteId = null;
if (this.isDemo) { if (this.isDemo) {
this.tasks = this.tasks.filter(t => t.id !== taskId); this.tasks = this.tasks.filter(t => t.id !== taskId);
if (this.detailTaskId === taskId) this.detailTaskId = null; if (this.detailTaskId === taskId) this.detailTaskId = null;
@ -866,6 +888,22 @@ class FolkTasksBoard extends HTMLElement {
.col-editor__add input { flex: 1; padding: 6px 8px; border-radius: 6px; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 12px; outline: none; } .col-editor__add input { flex: 1; padding: 6px 8px; border-radius: 6px; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 12px; outline: none; }
.col-editor__add button { padding: 6px 14px; border-radius: 6px; border: none; background: var(--rs-primary); color: #fff; cursor: pointer; font-size: 12px; font-weight: 600; } .col-editor__add button { padding: 6px 14px; border-radius: 6px; border: none; background: var(--rs-primary); color: #fff; cursor: pointer; font-size: 12px; font-weight: 600; }
/* Inline workspace create form */
.ws-create-form { background: var(--rs-bg-surface); border: 1px solid var(--rs-primary); border-radius: 10px; padding: 14px; margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
.ws-create-form input { flex: 1; padding: 8px 12px; border-radius: 6px; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 14px; outline: none; }
.ws-create-form input:focus { border-color: var(--rs-primary-hover); }
.ws-create-form button { padding: 8px 16px; border-radius: 6px; border: none; font-size: 13px; cursor: pointer; font-weight: 600; }
.ws-create-submit { background: var(--rs-primary); color: #fff; }
.ws-create-submit:hover { background: var(--rs-primary-hover); }
.ws-create-cancel { background: transparent; color: var(--rs-text-muted); border: 1px solid var(--rs-border-strong) !important; }
/* Inline delete confirmation */
.confirm-delete { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: #3b1111; border-radius: 6px; margin-top: 6px; }
.confirm-delete span { font-size: 12px; color: #f87171; }
.confirm-delete button { padding: 4px 10px; border-radius: 4px; border: none; font-size: 11px; cursor: pointer; font-weight: 600; }
.confirm-yes { background: #f87171; color: #fff; }
.confirm-no { background: transparent; color: var(--rs-text-muted); border: 1px solid var(--rs-border-strong) !important; }
/* Gear icon */ /* Gear icon */
.gear-btn { padding: 4px 8px; border-radius: 6px; border: 1px solid var(--rs-border-subtle); background: transparent; color: var(--rs-text-muted); cursor: pointer; font-size: 14px; } .gear-btn { padding: 4px 8px; border-radius: 6px; border: 1px solid var(--rs-border-subtle); background: transparent; color: var(--rs-text-muted); cursor: pointer; font-size: 14px; }
.gear-btn:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); } .gear-btn:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
@ -912,6 +950,13 @@ class FolkTasksBoard extends HTMLElement {
<button class="rapp-nav__btn" id="create-ws">+ New Workspace</button> <button class="rapp-nav__btn" id="create-ws">+ New Workspace</button>
<button class="rapp-nav__btn" id="btn-tour" style="font-size:0.78rem;padding:4px 10px;opacity:0.7">Tour</button> <button class="rapp-nav__btn" id="btn-tour" style="font-size:0.78rem;padding:4px 10px;opacity:0.7">Tour</button>
</div> </div>
${this._showCreateWs ? `
<div class="ws-create-form">
<input type="text" id="ws-name-input" placeholder="Workspace name..." autofocus>
<button class="ws-create-submit" id="ws-create-submit">Create</button>
<button class="ws-create-cancel" id="ws-create-cancel">Cancel</button>
</div>
` : ''}
${this.renderClickUpPanel()} ${this.renderClickUpPanel()}
${this.workspaces.length > 0 ? `<div class="workspace-grid"> ${this.workspaces.length > 0 ? `<div class="workspace-grid">
${this.workspaces.map(ws => ` ${this.workspaces.map(ws => `
@ -1060,6 +1105,7 @@ class FolkTasksBoard extends HTMLElement {
return `<span class="task-due${overdue ? ' overdue' : ''}">${fmt}</span>`; return `<span class="task-due${overdue ? ' overdue' : ''}">${fmt}</span>`;
})(); })();
const labelFilter = this._filterLabel; const labelFilter = this._filterLabel;
const confirmingDelete = this._confirmDeleteId === task.id;
return ` return `
<div class="task-card" draggable="${isEditing ? "false" : "true"}" data-task-id="${task.id}" data-collab-id="task:${task.id}"> <div class="task-card" draggable="${isEditing ? "false" : "true"}" data-task-id="${task.id}" data-collab-id="task:${task.id}">
<button class="task-delete-btn" data-quick-delete="${task.id}" title="Delete">&times;</button> <button class="task-delete-btn" data-quick-delete="${task.id}" title="Delete">&times;</button>
@ -1072,6 +1118,7 @@ class FolkTasksBoard extends HTMLElement {
${dueDateBadge} ${dueDateBadge}
${(task.labels || []).map((l: string) => `<span class="badge clickable${labelFilter === l ? ' filter-active' : ''}" data-filter-label="${l}">${this.esc(l)}</span>`).join("")} ${(task.labels || []).map((l: string) => `<span class="badge clickable${labelFilter === l ? ' filter-active' : ''}" data-filter-label="${l}">${this.esc(l)}</span>`).join("")}
</div> </div>
${confirmingDelete ? `<div class="confirm-delete"><span>Delete?</span><button class="confirm-yes" data-confirm-yes="${task.id}">Yes</button><button class="confirm-no" data-confirm-no="${task.id}">No</button></div>` : ''}
</div> </div>
`; `;
} }
@ -1133,7 +1180,9 @@ class FolkTasksBoard extends HTMLElement {
<label>Updated</label> <label>Updated</label>
<span class="readonly">${task.updated_at ? new Date(task.updated_at).toLocaleString() : new Date(task.updatedAt || Date.now()).toLocaleString()}</span> <span class="readonly">${task.updated_at ? new Date(task.updated_at).toLocaleString() : new Date(task.updatedAt || Date.now()).toLocaleString()}</span>
</div> </div>
<button class="detail-delete" id="detail-delete">Delete Task</button> ${this._confirmDeleteId === task.id
? `<div class="confirm-delete"><span>Delete this task?</span><button class="confirm-yes" data-confirm-yes="${task.id}">Yes, delete</button><button class="confirm-no" data-confirm-no="${task.id}">Cancel</button></div>`
: `<button class="detail-delete" id="detail-delete">Delete Task</button>`}
</div> </div>
`; `;
} }
@ -1169,7 +1218,25 @@ class FolkTasksBoard extends HTMLElement {
this.render(); this.render();
}); });
this.shadow.querySelector("#btn-tour")?.addEventListener("click", () => this.startTour()); this.shadow.querySelector("#btn-tour")?.addEventListener("click", () => this.startTour());
this.shadow.getElementById("create-ws")?.addEventListener("click", () => this.createWorkspace()); this.shadow.getElementById("create-ws")?.addEventListener("click", () => {
this._showCreateWs = !this._showCreateWs;
this.render();
if (this._showCreateWs) setTimeout(() => this.shadow.getElementById("ws-name-input")?.focus(), 0);
});
this.shadow.getElementById("ws-create-submit")?.addEventListener("click", () => {
const input = this.shadow.getElementById("ws-name-input") as HTMLInputElement;
if (input?.value?.trim()) this.createWorkspace(input.value.trim());
});
this.shadow.getElementById("ws-name-input")?.addEventListener("keydown", (e) => {
if ((e as KeyboardEvent).key === "Enter") {
const input = e.target as HTMLInputElement;
if (input.value?.trim()) this.createWorkspace(input.value.trim());
}
if ((e as KeyboardEvent).key === "Escape") { this._showCreateWs = false; this.render(); }
});
this.shadow.getElementById("ws-create-cancel")?.addEventListener("click", () => {
this._showCreateWs = false; this.render();
});
this.shadow.getElementById("create-task")?.addEventListener("click", () => { this.shadow.getElementById("create-task")?.addEventListener("click", () => {
this.showCreateForm = !this.showCreateForm; this.showCreateForm = !this.showCreateForm;
this.render(); this.render();
@ -1344,7 +1411,7 @@ class FolkTasksBoard extends HTMLElement {
}); });
// Detail delete // Detail delete
this.shadow.getElementById("detail-delete")?.addEventListener("click", () => { this.shadow.getElementById("detail-delete")?.addEventListener("click", () => {
if (this.detailTaskId && confirm("Delete this task?")) this.deleteTask(this.detailTaskId); if (this.detailTaskId) this.confirmDelete(this.detailTaskId);
}); });
// ── Quick delete on card hover ── // ── Quick delete on card hover ──
@ -1352,7 +1419,22 @@ class FolkTasksBoard extends HTMLElement {
el.addEventListener("click", (e) => { el.addEventListener("click", (e) => {
e.stopPropagation(); e.stopPropagation();
const taskId = (el as HTMLElement).dataset.quickDelete!; const taskId = (el as HTMLElement).dataset.quickDelete!;
if (confirm("Delete this task?")) this.deleteTask(taskId); this.confirmDelete(taskId);
});
});
// ── Inline delete confirmation ──
this.shadow.querySelectorAll("[data-confirm-yes]").forEach(el => {
el.addEventListener("click", (e) => {
e.stopPropagation();
const taskId = (el as HTMLElement).dataset.confirmYes!;
this.deleteTask(taskId);
});
});
this.shadow.querySelectorAll("[data-confirm-no]").forEach(el => {
el.addEventListener("click", (e) => {
e.stopPropagation();
this.cancelDelete();
}); });
}); });