clay-server 2.47.0-beta.5 → 2.47.0-beta.6
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/lib/project-http.js +5 -2
- package/lib/public/app.js +4 -1
- package/lib/public/css/icon-strip.css +0 -190
- package/lib/public/css/mobile-nav.css +4 -0
- package/lib/public/css/title-bar.css +169 -0
- package/lib/public/index.html +6 -0
- package/lib/public/modules/app-favicon.js +1 -10
- package/lib/public/modules/app-home-hub.js +2 -0
- package/lib/public/modules/app-messages.js +4 -7
- package/lib/public/modules/app-projects.js +43 -30
- package/lib/public/modules/branch-switcher.js +178 -0
- package/lib/public/modules/dom-refs.js +0 -1
- package/lib/public/modules/project-switcher.js +2 -1
- package/lib/public/modules/sidebar-mobile.js +19 -6
- package/lib/public/modules/sidebar-projects.js +63 -236
- package/lib/public/modules/worktree-family.js +60 -0
- package/package.json +1 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Title-bar branch switcher for a project and its worktrees.
|
|
2
|
+
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
import { escapeHtml } from './utils.js';
|
|
6
|
+
import { familyOf } from './worktree-family.js';
|
|
7
|
+
import { switchProject, confirmRemoveProject } from './app-projects.js';
|
|
8
|
+
import { showWorktreeModal } from './sidebar-projects.js';
|
|
9
|
+
|
|
10
|
+
var chip = null;
|
|
11
|
+
var label = null;
|
|
12
|
+
var menu = null;
|
|
13
|
+
var menuOpen = false;
|
|
14
|
+
var defaultBranches = {};
|
|
15
|
+
// slug -> true/false once /api/branches answers; undefined while unknown.
|
|
16
|
+
// Non-git projects hide the chip (no worktrees to manage there).
|
|
17
|
+
var gitRepoBySlug = {};
|
|
18
|
+
|
|
19
|
+
function projectName(project) {
|
|
20
|
+
return project ? (project.title || project.project || project.name || project.slug) : "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function currentProject(projects) {
|
|
24
|
+
var slug = store.get('currentSlug');
|
|
25
|
+
for (var i = 0; i < projects.length; i++) {
|
|
26
|
+
if (projects[i].slug === slug) return projects[i];
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function defaultBranch(parent) {
|
|
32
|
+
return defaultBranches[parent.slug] || "default";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestDefaultBranch(parent) {
|
|
36
|
+
if (!parent || Object.prototype.hasOwnProperty.call(defaultBranches, parent.slug)) return;
|
|
37
|
+
defaultBranches[parent.slug] = null;
|
|
38
|
+
fetch("/p/" + encodeURIComponent(parent.slug) + "/api/branches")
|
|
39
|
+
.then(function (response) { return response.json(); })
|
|
40
|
+
.then(function (data) {
|
|
41
|
+
defaultBranches[parent.slug] = data.defaultBranch || "default";
|
|
42
|
+
gitRepoBySlug[parent.slug] = data.isGitRepo !== false;
|
|
43
|
+
renderBranchSwitcher();
|
|
44
|
+
})
|
|
45
|
+
.catch(function () {
|
|
46
|
+
defaultBranches[parent.slug] = "default";
|
|
47
|
+
renderBranchSwitcher();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function closeMenu() {
|
|
52
|
+
menuOpen = false;
|
|
53
|
+
if (menu) menu.classList.add("hidden");
|
|
54
|
+
if (chip) {
|
|
55
|
+
chip.classList.remove("open");
|
|
56
|
+
chip.setAttribute("aria-expanded", "false");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function positionMenu() {
|
|
61
|
+
if (!chip || !menu || !menuOpen) return;
|
|
62
|
+
var rect = chip.getBoundingClientRect();
|
|
63
|
+
var left = Math.min(rect.left, window.innerWidth - menu.offsetWidth - 8);
|
|
64
|
+
var top = rect.bottom + 6;
|
|
65
|
+
if (top + menu.offsetHeight > window.innerHeight - 8) top = rect.top - menu.offsetHeight - 6;
|
|
66
|
+
menu.style.left = Math.max(8, left) + "px";
|
|
67
|
+
menu.style.top = Math.max(8, top) + "px";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function branchLabel(project, parent) {
|
|
71
|
+
return project.isWorktree ? (project.branch || projectName(project)) : defaultBranch(parent);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function appendProjectRow(container, project, parent, isCurrent) {
|
|
75
|
+
var row = document.createElement("div");
|
|
76
|
+
row.className = "branch-chip-row" + (isCurrent ? " current" : "");
|
|
77
|
+
var inaccessible = project.isWorktree && project.worktreeAccessible === false;
|
|
78
|
+
var main = document.createElement("button");
|
|
79
|
+
main.className = "branch-chip-row-main";
|
|
80
|
+
main.disabled = inaccessible;
|
|
81
|
+
if (inaccessible) main.title = "Outside project path";
|
|
82
|
+
main.innerHTML = iconHtml("git-branch") + '<span class="branch-chip-row-label">' +
|
|
83
|
+
escapeHtml(branchLabel(project, parent)) + "</span>";
|
|
84
|
+
if (project.isProcessing) {
|
|
85
|
+
var dot = document.createElement("span");
|
|
86
|
+
dot.className = "branch-chip-processing";
|
|
87
|
+
main.appendChild(dot);
|
|
88
|
+
}
|
|
89
|
+
if (project.pendingPermissions > 0) {
|
|
90
|
+
var badge = document.createElement("span");
|
|
91
|
+
badge.className = "branch-chip-pending";
|
|
92
|
+
badge.textContent = project.pendingPermissions > 99 ? "99+" : String(project.pendingPermissions);
|
|
93
|
+
main.appendChild(badge);
|
|
94
|
+
}
|
|
95
|
+
if (isCurrent) main.insertAdjacentHTML("beforeend", iconHtml("check"));
|
|
96
|
+
if (!inaccessible) {
|
|
97
|
+
main.addEventListener("click", function () { closeMenu(); switchProject(project.slug); });
|
|
98
|
+
}
|
|
99
|
+
row.appendChild(main);
|
|
100
|
+
|
|
101
|
+
if (project.isWorktree && (!store.get('permissions') || store.get('permissions').deleteProject !== false)) {
|
|
102
|
+
var remove = document.createElement("button");
|
|
103
|
+
remove.className = "branch-chip-row-action";
|
|
104
|
+
remove.title = "Remove worktree";
|
|
105
|
+
remove.innerHTML = iconHtml("trash-2");
|
|
106
|
+
remove.addEventListener("click", function (event) {
|
|
107
|
+
event.stopPropagation();
|
|
108
|
+
closeMenu();
|
|
109
|
+
confirmRemoveProject(project.slug, projectName(project));
|
|
110
|
+
});
|
|
111
|
+
row.appendChild(remove);
|
|
112
|
+
}
|
|
113
|
+
container.appendChild(row);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function renderMenu(family, current) {
|
|
117
|
+
menu.innerHTML = "";
|
|
118
|
+
if (family.parent) appendProjectRow(menu, family.parent, family.parent, current.slug === family.parent.slug);
|
|
119
|
+
for (var i = 0; i < family.worktrees.length; i++) {
|
|
120
|
+
appendProjectRow(menu, family.worktrees[i], family.parent || family.worktrees[i], current.slug === family.worktrees[i].slug);
|
|
121
|
+
}
|
|
122
|
+
if (family.parent) {
|
|
123
|
+
var footer = document.createElement("button");
|
|
124
|
+
footer.className = "branch-chip-new";
|
|
125
|
+
footer.innerHTML = iconHtml("plus") + " <span>New worktree...</span>";
|
|
126
|
+
footer.addEventListener("click", function () {
|
|
127
|
+
closeMenu();
|
|
128
|
+
showWorktreeModal(family.parent.slug, projectName(family.parent));
|
|
129
|
+
});
|
|
130
|
+
menu.appendChild(footer);
|
|
131
|
+
}
|
|
132
|
+
refreshIcons();
|
|
133
|
+
positionMenu();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function renderBranchSwitcher() {
|
|
137
|
+
if (!chip) return;
|
|
138
|
+
var projects = store.get('projects') || [];
|
|
139
|
+
var current = currentProject(projects);
|
|
140
|
+
var family = current ? familyOf(projects, current.slug) : { parent: null, worktrees: [] };
|
|
141
|
+
// The chip is the project's worktree management surface, so it shows even
|
|
142
|
+
// with zero worktrees (the menu then offers the default branch and "New
|
|
143
|
+
// worktree"). Hidden where there is no project context to manage, and for
|
|
144
|
+
// non-git projects (optimistically visible until /api/branches answers).
|
|
145
|
+
var gateSlug = family.parent ? family.parent.slug : (current ? current.slug : null);
|
|
146
|
+
var hidden = !current || store.get('dmMode') || store.get('mateProjectSlug') ||
|
|
147
|
+
store.get('homeHubVisible') || (gateSlug && gitRepoBySlug[gateSlug] === false);
|
|
148
|
+
chip.classList.toggle("hidden", hidden);
|
|
149
|
+
if (hidden) { closeMenu(); return; }
|
|
150
|
+
if (family.parent) requestDefaultBranch(family.parent);
|
|
151
|
+
label.textContent = branchLabel(current, family.parent || current);
|
|
152
|
+
if (menuOpen) renderMenu(family, current);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function initBranchSwitcher() {
|
|
156
|
+
chip = document.getElementById("branch-chip");
|
|
157
|
+
label = document.getElementById("branch-chip-label");
|
|
158
|
+
menu = document.getElementById("branch-chip-menu");
|
|
159
|
+
if (!chip || !label || !menu) return;
|
|
160
|
+
chip.addEventListener("click", function (event) {
|
|
161
|
+
event.stopPropagation();
|
|
162
|
+
menuOpen = !menuOpen;
|
|
163
|
+
chip.classList.toggle("open", menuOpen);
|
|
164
|
+
chip.setAttribute("aria-expanded", menuOpen ? "true" : "false");
|
|
165
|
+
menu.classList.toggle("hidden", !menuOpen);
|
|
166
|
+
renderBranchSwitcher();
|
|
167
|
+
});
|
|
168
|
+
document.addEventListener("click", function (event) {
|
|
169
|
+
if (menuOpen && !menu.contains(event.target) && !chip.contains(event.target)) closeMenu();
|
|
170
|
+
});
|
|
171
|
+
window.addEventListener("resize", positionMenu);
|
|
172
|
+
store.subscribe(function (state, prev) {
|
|
173
|
+
if (state.projects !== prev.projects || state.currentSlug !== prev.currentSlug ||
|
|
174
|
+
state.dmMode !== prev.dmMode || state.mateProjectSlug !== prev.mateProjectSlug ||
|
|
175
|
+
state.homeHubVisible !== prev.homeHubVisible) renderBranchSwitcher();
|
|
176
|
+
});
|
|
177
|
+
renderBranchSwitcher();
|
|
178
|
+
}
|
|
@@ -16,6 +16,5 @@ export function getSessionListEl() { return ref("session-list"); }
|
|
|
16
16
|
|
|
17
17
|
export function getStatusDot() {
|
|
18
18
|
return document.querySelector("#icon-strip-projects .icon-strip-item.active .icon-strip-status") ||
|
|
19
|
-
document.querySelector("#icon-strip-projects .icon-strip-wt-item.active .icon-strip-status") ||
|
|
20
19
|
document.querySelector("#icon-strip-users .icon-strip-mate.active .icon-strip-status");
|
|
21
20
|
}
|
|
@@ -19,6 +19,7 @@ import { getCachedProjects, switchProject } from './app-projects.js';
|
|
|
19
19
|
import { openDm } from './app-dm.js';
|
|
20
20
|
import { mateAvatarUrl } from './avatar.js';
|
|
21
21
|
import { parseEmojis } from './markdown.js';
|
|
22
|
+
import { switcherProjectName } from './worktree-family.js';
|
|
22
23
|
|
|
23
24
|
var _projectMru = []; // project slugs, most recent first
|
|
24
25
|
var _mateMru = []; // mate ids (mate_XXX), most recent first
|
|
@@ -263,7 +264,7 @@ function buildProjectEntries() {
|
|
|
263
264
|
var unreachable = p.isWorktree && p.worktreeAccessible === false;
|
|
264
265
|
return {
|
|
265
266
|
key: p.slug,
|
|
266
|
-
title:
|
|
267
|
+
title: switcherProjectName(projects, p),
|
|
267
268
|
icon: p.icon || null,
|
|
268
269
|
fallbackIcon: 'folder',
|
|
269
270
|
isCurrent: p.slug === store.get('currentSlug'),
|
|
@@ -38,6 +38,7 @@ import { showHomeHub } from './app-home-hub.js';
|
|
|
38
38
|
import { openTerminal } from './terminal.js';
|
|
39
39
|
import { requestKnowledgeList } from './mate-knowledge.js';
|
|
40
40
|
import { loadRootDirectory } from './filebrowser.js';
|
|
41
|
+
import { aggregateFamily, familyOf, parentProjects } from './worktree-family.js';
|
|
41
42
|
|
|
42
43
|
// --- Mobile state ---
|
|
43
44
|
var mobileChatSheetOpen = false;
|
|
@@ -147,10 +148,22 @@ function closeMobileSheet() {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
function renderSheetProjects(listEl) {
|
|
150
|
-
|
|
151
|
+
var projects = getCachedProjectList();
|
|
152
|
+
var parents = parentProjects(projects);
|
|
153
|
+
var activeFamily = familyOf(projects, getCachedCurrentSlug());
|
|
154
|
+
var activeSlug = activeFamily.parent ? activeFamily.parent.slug : getCachedCurrentSlug();
|
|
155
|
+
|
|
156
|
+
for (var i = 0; i < parents.length; i++) {
|
|
151
157
|
(function (p) {
|
|
158
|
+
var family = familyOf(projects, p.slug);
|
|
159
|
+
var displayProject = aggregateFamily(p, family.worktrees);
|
|
160
|
+
var hasWorktreeUnread = family.worktrees.some(function (worktree) { return worktree.unread > 0; });
|
|
161
|
+
var hasWorktreePending = family.worktrees.some(function (worktree) { return worktree.pendingPermissions > 0; });
|
|
152
162
|
var el = document.createElement("button");
|
|
153
|
-
el.className = "mobile-project-item" + (p.slug ===
|
|
163
|
+
el.className = "mobile-project-item" + (p.slug === activeSlug ? " active" : "");
|
|
164
|
+
if ((displayProject.pendingPermissions > 0 && p.slug !== activeSlug) || hasWorktreePending) {
|
|
165
|
+
el.classList.add("has-pending-perm");
|
|
166
|
+
}
|
|
154
167
|
|
|
155
168
|
var abbrev = document.createElement("span");
|
|
156
169
|
abbrev.className = "mobile-project-abbrev";
|
|
@@ -167,16 +180,16 @@ function renderSheetProjects(listEl) {
|
|
|
167
180
|
name.textContent = p.name;
|
|
168
181
|
el.appendChild(name);
|
|
169
182
|
|
|
170
|
-
if (
|
|
183
|
+
if (displayProject.isProcessing) {
|
|
171
184
|
var dot = document.createElement("span");
|
|
172
185
|
dot.className = "mobile-project-processing";
|
|
173
186
|
el.appendChild(dot);
|
|
174
187
|
}
|
|
175
188
|
|
|
176
|
-
if (
|
|
189
|
+
if (displayProject.unread > 0 && (p.slug !== activeSlug || hasWorktreeUnread)) {
|
|
177
190
|
var mBadge = document.createElement("span");
|
|
178
191
|
mBadge.className = "mobile-project-unread";
|
|
179
|
-
mBadge.textContent =
|
|
192
|
+
mBadge.textContent = displayProject.unread > 99 ? "99+" : String(displayProject.unread);
|
|
180
193
|
el.appendChild(mBadge);
|
|
181
194
|
}
|
|
182
195
|
|
|
@@ -186,7 +199,7 @@ function renderSheetProjects(listEl) {
|
|
|
186
199
|
});
|
|
187
200
|
|
|
188
201
|
listEl.appendChild(el);
|
|
189
|
-
})(
|
|
202
|
+
})(parents[i]);
|
|
190
203
|
}
|
|
191
204
|
}
|
|
192
205
|
|
|
@@ -10,9 +10,10 @@ import { store } from './store.js';
|
|
|
10
10
|
import { getWs } from './ws-ref.js';
|
|
11
11
|
import { closeSidebar } from './sidebar.js';
|
|
12
12
|
import { showIconTooltip, hideIconTooltip, closeUserCtxMenu, getCurrentDmUserId } from './sidebar-mates.js';
|
|
13
|
-
import { switchProject, openAddProjectModal
|
|
13
|
+
import { switchProject, openAddProjectModal } from './app-projects.js';
|
|
14
14
|
import { showHomeHub } from './app-home-hub.js';
|
|
15
15
|
import { getPendingTuiAttention } from './app-notifications.js';
|
|
16
|
+
import { aggregateFamily } from './worktree-family.js';
|
|
16
17
|
|
|
17
18
|
// --- Project state ---
|
|
18
19
|
var cachedProjectList = [];
|
|
@@ -31,12 +32,6 @@ var emojiPickerEl = null;
|
|
|
31
32
|
var draggedSlug = null;
|
|
32
33
|
var draggedEl = null;
|
|
33
34
|
|
|
34
|
-
// --- Worktree folder collapse state (persisted in localStorage) ---
|
|
35
|
-
var wtCollapsed = {};
|
|
36
|
-
try {
|
|
37
|
-
wtCollapsed = JSON.parse(localStorage.getItem("clay-wt-collapsed") || "{}");
|
|
38
|
-
} catch (e) {}
|
|
39
|
-
|
|
40
35
|
var EMOJI_CATEGORIES = [
|
|
41
36
|
{ id: "frequent", icon: "🕐", label: "Frequent", emojis: [
|
|
42
37
|
"😀","😎","🤓","🧠","💡","🔥","⚡","🚀",
|
|
@@ -524,17 +519,8 @@ function showIconCtxMenu(anchorEl, slug, name) {
|
|
|
524
519
|
}
|
|
525
520
|
});
|
|
526
521
|
menu.appendChild(removeWtItem);
|
|
527
|
-
} else {
|
|
528
|
-
var wtItem = document.createElement("button");
|
|
529
|
-
wtItem.className = "project-ctx-item";
|
|
530
|
-
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
531
|
-
wtItem.addEventListener("click", function (e) {
|
|
532
|
-
e.stopPropagation();
|
|
533
|
-
closeProjectCtxMenu();
|
|
534
|
-
showWorktreeModal(slug, name || slug);
|
|
535
|
-
});
|
|
536
|
-
menu.appendChild(wtItem);
|
|
537
522
|
}
|
|
523
|
+
// Worktree creation lives in the title-bar branch chip, not here.
|
|
538
524
|
|
|
539
525
|
document.body.appendChild(menu);
|
|
540
526
|
projectCtxMenu = menu;
|
|
@@ -619,20 +605,7 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
619
605
|
}
|
|
620
606
|
}
|
|
621
607
|
|
|
622
|
-
|
|
623
|
-
sep2.className = "project-ctx-separator";
|
|
624
|
-
menu.appendChild(sep2);
|
|
625
|
-
|
|
626
|
-
// --- Add Worktree ---
|
|
627
|
-
var wtItem = document.createElement("button");
|
|
628
|
-
wtItem.className = "project-ctx-item";
|
|
629
|
-
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
630
|
-
wtItem.addEventListener("click", function (e) {
|
|
631
|
-
e.stopPropagation();
|
|
632
|
-
closeProjectCtxMenu();
|
|
633
|
-
showWorktreeModal(slug, name || slug);
|
|
634
|
-
});
|
|
635
|
-
menu.appendChild(wtItem);
|
|
608
|
+
// Worktree creation lives in the title-bar branch chip, not here.
|
|
636
609
|
|
|
637
610
|
if (!store.get('permissions') || store.get('permissions').deleteProject !== false) {
|
|
638
611
|
var sep3 = document.createElement("div");
|
|
@@ -953,13 +926,6 @@ function setupDragHandlers(el, slug) {
|
|
|
953
926
|
});
|
|
954
927
|
}
|
|
955
928
|
|
|
956
|
-
// --- Worktree folder collapse ---
|
|
957
|
-
|
|
958
|
-
function setWtCollapsed(slug, collapsed) {
|
|
959
|
-
wtCollapsed[slug] = collapsed;
|
|
960
|
-
try { localStorage.setItem("clay-wt-collapsed", JSON.stringify(wtCollapsed)); } catch (e) {}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
929
|
function groupProjects(projects) {
|
|
964
930
|
var parents = [];
|
|
965
931
|
var wtByParent = {};
|
|
@@ -975,6 +941,21 @@ function groupProjects(projects) {
|
|
|
975
941
|
return { parents: parents, wtByParent: wtByParent };
|
|
976
942
|
}
|
|
977
943
|
|
|
944
|
+
function aggregateProjectFamily(parent, worktrees) {
|
|
945
|
+
var aggregate = aggregateFamily(parent, worktrees);
|
|
946
|
+
aggregate.hasFamilyPendingAttention = getPendingTuiAttention(parent.slug) > 0;
|
|
947
|
+
aggregate.hasWorktreeUnread = false;
|
|
948
|
+
aggregate.hasWorktreePending = false;
|
|
949
|
+
for (var i = 0; i < worktrees.length; i++) {
|
|
950
|
+
if (getPendingTuiAttention(worktrees[i].slug) > 0) aggregate.hasFamilyPendingAttention = true;
|
|
951
|
+
if (worktrees[i].unread > 0) aggregate.hasWorktreeUnread = true;
|
|
952
|
+
if (worktrees[i].pendingPermissions > 0 || getPendingTuiAttention(worktrees[i].slug) > 0) {
|
|
953
|
+
aggregate.hasWorktreePending = true;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return aggregate;
|
|
957
|
+
}
|
|
958
|
+
|
|
978
959
|
// --- Icon item creation ---
|
|
979
960
|
|
|
980
961
|
function createIconItem(p, currentSlug) {
|
|
@@ -1006,7 +987,7 @@ function createIconItem(p, currentSlug) {
|
|
|
1006
987
|
|
|
1007
988
|
var projectBadge = document.createElement("span");
|
|
1008
989
|
projectBadge.className = "icon-strip-project-badge";
|
|
1009
|
-
if (p.unread > 0 && !isActive) {
|
|
990
|
+
if (p.unread > 0 && (!isActive || p.hasWorktreeUnread)) {
|
|
1010
991
|
projectBadge.textContent = p.unread > 99 ? "99+" : String(p.unread);
|
|
1011
992
|
projectBadge.classList.add("has-unread");
|
|
1012
993
|
}
|
|
@@ -1016,7 +997,8 @@ function createIconItem(p, currentSlug) {
|
|
|
1016
997
|
// broadcast — they surface via tui_attention notifications. We shake the
|
|
1017
998
|
// same icon for either, so the user notices regardless of which engine
|
|
1018
999
|
// is asking.
|
|
1019
|
-
if (!isActive && (p.pendingPermissions > 0 || getPendingTuiAttention(p.slug) > 0))
|
|
1000
|
+
if ((!isActive && (p.pendingPermissions > 0 || p.hasFamilyPendingAttention || getPendingTuiAttention(p.slug) > 0)) ||
|
|
1001
|
+
p.hasWorktreePending) {
|
|
1020
1002
|
el.classList.add("has-pending-perm");
|
|
1021
1003
|
}
|
|
1022
1004
|
|
|
@@ -1037,7 +1019,7 @@ function createIconItem(p, currentSlug) {
|
|
|
1037
1019
|
|
|
1038
1020
|
// --- Worktree creation modal ---
|
|
1039
1021
|
|
|
1040
|
-
function showWorktreeModal(parentSlug, parentName) {
|
|
1022
|
+
export function showWorktreeModal(parentSlug, parentName) {
|
|
1041
1023
|
var existing = document.getElementById("wt-modal-container");
|
|
1042
1024
|
if (existing) existing.remove();
|
|
1043
1025
|
|
|
@@ -1192,164 +1174,17 @@ export function renderIconStrip(projects, currentSlug) {
|
|
|
1192
1174
|
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1193
1175
|
var p = grouped.parents[i];
|
|
1194
1176
|
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1195
|
-
var
|
|
1196
|
-
|
|
1197
|
-
if (!hasWorktrees) {
|
|
1198
|
-
var el = createIconItem(p, currentSlug);
|
|
1199
|
-
(function (slug, name, elem) {
|
|
1200
|
-
elem.addEventListener("contextmenu", function (e) {
|
|
1201
|
-
e.preventDefault();
|
|
1202
|
-
e.stopPropagation();
|
|
1203
|
-
showIconCtxMenu(elem, slug, name);
|
|
1204
|
-
});
|
|
1205
|
-
})(p.slug, p.name || p.slug, el);
|
|
1206
|
-
setupDragHandlers(el, p.slug);
|
|
1207
|
-
container.appendChild(el);
|
|
1208
|
-
continue;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// Folder group for parent + worktrees
|
|
1212
|
-
var folder = document.createElement("div");
|
|
1213
|
-
folder.className = "icon-strip-group";
|
|
1214
|
-
folder.dataset.parentSlug = p.slug;
|
|
1215
|
-
if (wtCollapsed[p.slug]) folder.classList.add("collapsed");
|
|
1216
|
-
|
|
1217
|
-
if (!p.isProcessing) {
|
|
1218
|
-
for (var wpi = 0; wpi < worktrees.length; wpi++) {
|
|
1219
|
-
if (worktrees[wpi].isProcessing) { p.isProcessing = true; break; }
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
var header = createIconItem(p, currentSlug);
|
|
1224
|
-
header.classList.add("folder-header");
|
|
1177
|
+
var familyProject = aggregateProjectFamily(p, worktrees);
|
|
1178
|
+
var el = createIconItem(familyProject, currentSlug);
|
|
1225
1179
|
(function (slug, name, elem) {
|
|
1226
1180
|
elem.addEventListener("contextmenu", function (e) {
|
|
1227
1181
|
e.preventDefault();
|
|
1228
1182
|
e.stopPropagation();
|
|
1229
1183
|
showIconCtxMenu(elem, slug, name);
|
|
1230
1184
|
});
|
|
1231
|
-
})(p.slug, p.name || p.slug,
|
|
1232
|
-
setupDragHandlers(
|
|
1233
|
-
|
|
1234
|
-
var chevron = document.createElement("span");
|
|
1235
|
-
chevron.className = "icon-strip-group-chevron";
|
|
1236
|
-
chevron.innerHTML = '<i data-lucide="git-branch"></i>';
|
|
1237
|
-
(function (parentSlug, folderEl) {
|
|
1238
|
-
chevron.addEventListener("click", function (e) {
|
|
1239
|
-
e.preventDefault();
|
|
1240
|
-
e.stopPropagation();
|
|
1241
|
-
var nowCollapsed = folderEl.classList.toggle("collapsed");
|
|
1242
|
-
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1243
|
-
});
|
|
1244
|
-
chevron.addEventListener("contextmenu", function (e) {
|
|
1245
|
-
e.preventDefault();
|
|
1246
|
-
e.stopPropagation();
|
|
1247
|
-
});
|
|
1248
|
-
})(p.slug, folder);
|
|
1249
|
-
chevron.setAttribute("data-tip", "Toggle worktrees");
|
|
1250
|
-
header.appendChild(chevron);
|
|
1251
|
-
folder.appendChild(header);
|
|
1252
|
-
|
|
1253
|
-
var itemsContainer = document.createElement("div");
|
|
1254
|
-
itemsContainer.className = "icon-strip-group-items";
|
|
1255
|
-
|
|
1256
|
-
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1257
|
-
(function (wt) {
|
|
1258
|
-
var wtEl = document.createElement("a");
|
|
1259
|
-
var isWtActive = wt.slug === currentSlug && !currentDmUserId;
|
|
1260
|
-
var isAccessible = wt.worktreeAccessible !== false;
|
|
1261
|
-
wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (!isAccessible ? " wt-disabled" : "");
|
|
1262
|
-
wtEl.href = "/p/" + wt.slug + "/";
|
|
1263
|
-
wtEl.dataset.slug = wt.slug;
|
|
1264
|
-
|
|
1265
|
-
if (wt.icon) {
|
|
1266
|
-
var wtEmoji = document.createElement("span");
|
|
1267
|
-
wtEmoji.className = "wt-branch-abbrev project-emoji";
|
|
1268
|
-
wtEmoji.textContent = wt.icon;
|
|
1269
|
-
parseEmojis(wtEmoji);
|
|
1270
|
-
wtEl.appendChild(wtEmoji);
|
|
1271
|
-
} else {
|
|
1272
|
-
var abbrev = document.createElement("span");
|
|
1273
|
-
abbrev.className = "wt-branch-abbrev";
|
|
1274
|
-
abbrev.textContent = getProjectAbbrev(wt.name);
|
|
1275
|
-
wtEl.appendChild(abbrev);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
var wtStatus = document.createElement("span");
|
|
1279
|
-
wtStatus.className = "icon-strip-status";
|
|
1280
|
-
if (wt.isProcessing) wtStatus.classList.add("processing");
|
|
1281
|
-
wtEl.appendChild(wtStatus);
|
|
1282
|
-
|
|
1283
|
-
var tooltipText = wt.name;
|
|
1284
|
-
if (!isAccessible) {
|
|
1285
|
-
tooltipText += " (outside project path, cannot be accessed)";
|
|
1286
|
-
}
|
|
1287
|
-
(function (text, elem) {
|
|
1288
|
-
elem.addEventListener("mouseenter", function () { showIconTooltip(elem, text); });
|
|
1289
|
-
elem.addEventListener("mouseleave", hideIconTooltip);
|
|
1290
|
-
})(tooltipText, wtEl);
|
|
1291
|
-
|
|
1292
|
-
if (isAccessible) {
|
|
1293
|
-
(function (slug) {
|
|
1294
|
-
wtEl.addEventListener("click", function (e) {
|
|
1295
|
-
e.preventDefault();
|
|
1296
|
-
if (switchProject) switchProject(slug);
|
|
1297
|
-
});
|
|
1298
|
-
})(wt.slug);
|
|
1299
|
-
} else {
|
|
1300
|
-
wtEl.addEventListener("click", function (e) {
|
|
1301
|
-
e.preventDefault();
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
if (isAccessible) {
|
|
1306
|
-
(function (slug, name, elem) {
|
|
1307
|
-
elem.addEventListener("contextmenu", function (e) {
|
|
1308
|
-
e.preventDefault();
|
|
1309
|
-
e.stopPropagation();
|
|
1310
|
-
showIconCtxMenu(elem, slug, name);
|
|
1311
|
-
});
|
|
1312
|
-
})(wt.slug, wt.name, wtEl);
|
|
1313
|
-
} else {
|
|
1314
|
-
wtEl.addEventListener("contextmenu", function (e) {
|
|
1315
|
-
e.preventDefault();
|
|
1316
|
-
e.stopPropagation();
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
if (!isWtActive && (wt.pendingPermissions > 0 || getPendingTuiAttention(wt.slug) > 0)) {
|
|
1321
|
-
wtEl.classList.add("has-pending-perm");
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
itemsContainer.appendChild(wtEl);
|
|
1325
|
-
})(worktrees[wi]);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
var hasWtPendingPerm = false;
|
|
1329
|
-
for (var wpi2 = 0; wpi2 < worktrees.length; wpi2++) {
|
|
1330
|
-
if (worktrees[wpi2].pendingPermissions > 0 || getPendingTuiAttention(worktrees[wpi2].slug) > 0) {
|
|
1331
|
-
hasWtPendingPerm = true;
|
|
1332
|
-
break;
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
if (hasWtPendingPerm) folder.classList.remove("collapsed");
|
|
1336
|
-
|
|
1337
|
-
var addBtn = document.createElement("button");
|
|
1338
|
-
addBtn.className = "icon-strip-group-add";
|
|
1339
|
-
addBtn.textContent = "+";
|
|
1340
|
-
(function (parentSlug, parentName, btn) {
|
|
1341
|
-
btn.addEventListener("click", function (e) {
|
|
1342
|
-
e.preventDefault();
|
|
1343
|
-
e.stopPropagation();
|
|
1344
|
-
showWorktreeModal(parentSlug, parentName);
|
|
1345
|
-
});
|
|
1346
|
-
btn.addEventListener("mouseenter", function () { showIconTooltip(btn, "New worktree"); });
|
|
1347
|
-
btn.addEventListener("mouseleave", hideIconTooltip);
|
|
1348
|
-
})(p.slug, p.name, addBtn);
|
|
1349
|
-
itemsContainer.appendChild(addBtn);
|
|
1350
|
-
|
|
1351
|
-
folder.appendChild(itemsContainer);
|
|
1352
|
-
container.appendChild(folder);
|
|
1185
|
+
})(p.slug, p.name || p.slug, el);
|
|
1186
|
+
setupDragHandlers(el, p.slug);
|
|
1187
|
+
container.appendChild(el);
|
|
1353
1188
|
}
|
|
1354
1189
|
|
|
1355
1190
|
// Update home icon active state
|
|
@@ -1377,50 +1212,17 @@ function renderProjectList(projects, currentSlug) {
|
|
|
1377
1212
|
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1378
1213
|
var p = grouped.parents[i];
|
|
1379
1214
|
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1380
|
-
|
|
1381
|
-
if (worktrees.length === 0) {
|
|
1382
|
-
list.appendChild(createMobileProjectItem(p, currentSlug, false));
|
|
1383
|
-
continue;
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
var folderDiv = document.createElement("div");
|
|
1387
|
-
folderDiv.className = "mobile-project-folder";
|
|
1388
|
-
if (wtCollapsed[p.slug]) folderDiv.classList.add("collapsed");
|
|
1389
|
-
|
|
1390
|
-
var headerEl = createMobileProjectItem(p, currentSlug, false);
|
|
1391
|
-
var chevron = document.createElement("span");
|
|
1392
|
-
chevron.className = "mobile-folder-chevron";
|
|
1393
|
-
chevron.innerHTML = "▼";
|
|
1394
|
-
(function (parentSlug, fDiv) {
|
|
1395
|
-
chevron.addEventListener("click", function (e) {
|
|
1396
|
-
e.preventDefault();
|
|
1397
|
-
e.stopPropagation();
|
|
1398
|
-
var nowCollapsed = fDiv.classList.toggle("collapsed");
|
|
1399
|
-
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1400
|
-
});
|
|
1401
|
-
})(p.slug, folderDiv);
|
|
1402
|
-
headerEl.appendChild(chevron);
|
|
1403
|
-
folderDiv.appendChild(headerEl);
|
|
1404
|
-
|
|
1405
|
-
var wtList = document.createElement("div");
|
|
1406
|
-
wtList.className = "mobile-folder-items";
|
|
1407
|
-
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1408
|
-
var isAccessible = worktrees[wi].worktreeAccessible !== false;
|
|
1409
|
-
var wtItem = createMobileProjectItem(worktrees[wi], currentSlug, true);
|
|
1410
|
-
if (!isAccessible) wtItem.classList.add("wt-disabled");
|
|
1411
|
-
if (!isAccessible) {
|
|
1412
|
-
wtItem.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); });
|
|
1413
|
-
}
|
|
1414
|
-
wtList.appendChild(wtItem);
|
|
1415
|
-
}
|
|
1416
|
-
folderDiv.appendChild(wtList);
|
|
1417
|
-
list.appendChild(folderDiv);
|
|
1215
|
+
list.appendChild(createMobileProjectItem(aggregateProjectFamily(p, worktrees), currentSlug));
|
|
1418
1216
|
}
|
|
1419
1217
|
}
|
|
1420
1218
|
|
|
1421
|
-
function createMobileProjectItem(p, currentSlug
|
|
1219
|
+
function createMobileProjectItem(p, currentSlug) {
|
|
1422
1220
|
var el = document.createElement("button");
|
|
1423
|
-
|
|
1221
|
+
var isActive = p.slug === currentSlug;
|
|
1222
|
+
el.className = "mobile-project-item" + (isActive ? " active" : "");
|
|
1223
|
+
if ((!isActive && (p.pendingPermissions > 0 || p.hasFamilyPendingAttention)) || p.hasWorktreePending) {
|
|
1224
|
+
el.classList.add("has-pending-perm");
|
|
1225
|
+
}
|
|
1424
1226
|
|
|
1425
1227
|
var abbrev = document.createElement("span");
|
|
1426
1228
|
abbrev.className = "mobile-project-abbrev";
|
|
@@ -1443,6 +1245,13 @@ function createMobileProjectItem(p, currentSlug, isWorktree) {
|
|
|
1443
1245
|
el.appendChild(dot);
|
|
1444
1246
|
}
|
|
1445
1247
|
|
|
1248
|
+
if (p.unread > 0 && (!isActive || p.hasWorktreeUnread)) {
|
|
1249
|
+
var badge = document.createElement("span");
|
|
1250
|
+
badge.className = "mobile-project-unread";
|
|
1251
|
+
badge.textContent = p.unread > 99 ? "99+" : String(p.unread);
|
|
1252
|
+
el.appendChild(badge);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1446
1255
|
el.addEventListener("click", function () {
|
|
1447
1256
|
if (switchProject) switchProject(p.slug);
|
|
1448
1257
|
if (closeSidebar) closeSidebar();
|
|
@@ -1454,12 +1263,30 @@ function createMobileProjectItem(p, currentSlug, isWorktree) {
|
|
|
1454
1263
|
export function getEmojiCategories() { return EMOJI_CATEGORIES; }
|
|
1455
1264
|
|
|
1456
1265
|
export function updateProjectBadge(slug, count) {
|
|
1457
|
-
var
|
|
1266
|
+
var targetSlug = slug;
|
|
1267
|
+
for (var i = 0; i < cachedProjectList.length; i++) {
|
|
1268
|
+
if (cachedProjectList[i].slug !== slug) continue;
|
|
1269
|
+
cachedProjectList[i].unread = count;
|
|
1270
|
+
if (cachedProjectList[i].isWorktree && cachedProjectList[i].parentSlug) {
|
|
1271
|
+
targetSlug = cachedProjectList[i].parentSlug;
|
|
1272
|
+
}
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
var displayCount = count;
|
|
1277
|
+
var grouped = groupProjects(cachedProjectList);
|
|
1278
|
+
for (var pi = 0; pi < grouped.parents.length; pi++) {
|
|
1279
|
+
if (grouped.parents[pi].slug !== targetSlug) continue;
|
|
1280
|
+
displayCount = aggregateFamily(grouped.parents[pi], grouped.wtByParent[targetSlug] || []).unread;
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
var icon = document.querySelector('.icon-strip-item[data-slug="' + targetSlug + '"]');
|
|
1458
1285
|
if (!icon) return;
|
|
1459
1286
|
var badge = icon.querySelector(".icon-strip-project-badge");
|
|
1460
1287
|
if (!badge) return;
|
|
1461
|
-
if (
|
|
1462
|
-
badge.textContent =
|
|
1288
|
+
if (displayCount > 0) {
|
|
1289
|
+
badge.textContent = displayCount > 99 ? "99+" : String(displayCount);
|
|
1463
1290
|
badge.classList.add("has-unread");
|
|
1464
1291
|
} else {
|
|
1465
1292
|
badge.textContent = "";
|