clay-server 2.27.0-beta.11 โ 2.27.0-beta.13
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.js +5 -1
- package/lib/public/app.js +1485 -8511
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +399 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +865 -0
- package/lib/public/modules/app-messages.js +1478 -0
- package/lib/public/modules/app-misc.js +301 -0
- package/lib/public/modules/app-panels.js +907 -0
- package/lib/public/modules/app-projects.js +782 -0
- package/lib/public/modules/app-rate-limit.js +448 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +240 -0
- package/lib/public/modules/sidebar-mates.js +801 -0
- package/lib/public/modules/sidebar-mobile.js +1259 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/sdk-bridge.js +54 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
// sidebar-projects.js - Project icon strip, context menus, emoji picker, drag-and-drop, worktree modal
|
|
2
|
+
// Extracted from sidebar.js (PR-36)
|
|
3
|
+
|
|
4
|
+
import { escapeHtml } from './utils.js';
|
|
5
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
|
+
import { openProjectSettings } from './project-settings.js';
|
|
7
|
+
import { triggerShare } from './qrcode.js';
|
|
8
|
+
import { parseEmojis } from './markdown.js';
|
|
9
|
+
|
|
10
|
+
var _ctx = null;
|
|
11
|
+
|
|
12
|
+
// --- Project state ---
|
|
13
|
+
var cachedProjectList = [];
|
|
14
|
+
var cachedCurrentSlug = null;
|
|
15
|
+
|
|
16
|
+
// --- Project context menu ---
|
|
17
|
+
var projectCtxMenu = null;
|
|
18
|
+
|
|
19
|
+
// --- Project Access Popover ---
|
|
20
|
+
var projectAccessPopover = null;
|
|
21
|
+
|
|
22
|
+
// --- Emoji picker ---
|
|
23
|
+
var emojiPickerEl = null;
|
|
24
|
+
|
|
25
|
+
// --- Drag-and-drop state ---
|
|
26
|
+
var draggedSlug = null;
|
|
27
|
+
var draggedEl = null;
|
|
28
|
+
|
|
29
|
+
// --- Worktree folder collapse state (persisted in localStorage) ---
|
|
30
|
+
var wtCollapsed = {};
|
|
31
|
+
try {
|
|
32
|
+
wtCollapsed = JSON.parse(localStorage.getItem("clay-wt-collapsed") || "{}");
|
|
33
|
+
} catch (e) {}
|
|
34
|
+
|
|
35
|
+
var EMOJI_CATEGORIES = [
|
|
36
|
+
{ id: "frequent", icon: "๐", label: "Frequent", emojis: [
|
|
37
|
+
"๐","๐","๐ค","๐ง ","๐ก","๐ฅ","โก","๐",
|
|
38
|
+
"๐ฏ","๐ฎ","๐จ","๐ต","๐ฆ","๐","๐","๐ป",
|
|
39
|
+
"๐ฅ๏ธ","โจ๏ธ","๐ง","๐ ๏ธ","โ๏ธ","๐งช","๐ฌ","๐งฌ",
|
|
40
|
+
"๐","๐ฑ","๐","๐ธ","๐","๐","โ๏ธ","๐",
|
|
41
|
+
"๐ฑ","๐ถ","๐ผ","๐ฆ","๐ฆ","๐","๐","๐ฆ",
|
|
42
|
+
"๐","๐","โ","๐ฉ","๐","๐","๐ง","๐ฃ",
|
|
43
|
+
"โค๏ธ","๐","๐","๐","๐","๐งก","๐ค","๐ค",
|
|
44
|
+
"โญ","โจ","๐","๐","๐","๐ช","๐ญ","๐",
|
|
45
|
+
]},
|
|
46
|
+
{ id: "smileys", icon: "๐", label: "Smileys & People", emojis: [
|
|
47
|
+
"๐","๐","๐","๐","๐","๐
","๐คฃ","๐",
|
|
48
|
+
"๐","๐","๐","๐ฅฐ","๐","๐คฉ","๐","๐",
|
|
49
|
+
"๐","๐","๐ฅฒ","๐","๐","๐","๐คช","๐",
|
|
50
|
+
"๐ค","๐ค","๐คญ","๐ซข","๐คซ","๐ค","๐ซก","๐ค",
|
|
51
|
+
"๐คจ","๐","๐","๐ถ","๐ซฅ","๐","๐","๐",
|
|
52
|
+
"๐ฌ","๐คฅ","๐","๐","๐ช","๐คค","๐ด","๐ท",
|
|
53
|
+
"๐ค","๐ค","๐คข","๐คฎ","๐ฅด","๐ต","๐คฏ","๐ฅณ",
|
|
54
|
+
"๐ฅธ","๐","๐ค","๐ง","๐","๐ซค","๐","๐",
|
|
55
|
+
"๐ฎ","๐ฏ","๐ฒ","๐ณ","๐ฅบ","๐ฅน","๐ฆ","๐ง",
|
|
56
|
+
"๐จ","๐ฐ","๐ฅ","๐ข","๐ญ","๐ฑ","๐","๐ฃ",
|
|
57
|
+
"๐","๐","๐ฉ","๐ซ","๐ฅฑ","๐ค","๐ก","๐ ",
|
|
58
|
+
"๐คฌ","๐","๐ฟ","๐","โ ๏ธ","๐ฉ","๐คก","๐น",
|
|
59
|
+
"๐บ","๐ป","๐ฝ","๐พ","๐ค","๐บ","๐ธ","๐น",
|
|
60
|
+
"๐ป","๐ผ","๐ฝ","๐","๐ฟ","๐พ","๐","๐",
|
|
61
|
+
"๐","๐","๐ค","๐๏ธ","โ","๐","๐ซฑ","๐ซฒ",
|
|
62
|
+
"๐ซณ","๐ซด","๐","๐ค","๐ค","โ๏ธ","๐ค","๐ซฐ",
|
|
63
|
+
"๐ค","๐ค","๐ค","๐","๐","๐","๐","๐",
|
|
64
|
+
"โ๏ธ","๐ซต","๐","๐","โ","๐","๐ค","๐ค",
|
|
65
|
+
"๐","๐","๐ซถ","๐","๐คฒ","๐ค","๐","๐ช",
|
|
66
|
+
]},
|
|
67
|
+
{ id: "animals", icon: "๐ป", label: "Animals & Nature", emojis: [
|
|
68
|
+
"๐ถ","๐ฑ","๐ญ","๐น","๐ฐ","๐ฆ","๐ป","๐ผ",
|
|
69
|
+
"๐ปโโ๏ธ","๐จ","๐ฏ","๐ฆ","๐ฎ","๐ท","๐ฝ","๐ธ",
|
|
70
|
+
"๐ต","๐","๐","๐","๐","๐","๐ง","๐ฆ",
|
|
71
|
+
"๐ค","๐ฃ","๐ฅ","๐ฆ","๐ฆ
","๐ฆ","๐ฆ","๐บ",
|
|
72
|
+
"๐","๐ด","๐ฆ","๐","๐ชฑ","๐","๐ฆ","๐",
|
|
73
|
+
"๐","๐","๐ชฐ","๐ชฒ","๐ชณ","๐ฆ","๐ฆ","๐ท๏ธ",
|
|
74
|
+
"๐ฆ","๐ข","๐","๐ฆ","๐ฆ","๐ฆ","๐","๐ฆ",
|
|
75
|
+
"๐ฆ","๐ฆ","๐ฆ","๐ชธ","๐ก","๐ ","๐","๐ฌ",
|
|
76
|
+
"๐ณ","๐","๐ฆ","๐","๐
","๐","๐ฆ","๐ซ",
|
|
77
|
+
"๐ฆ","๐ฆง","๐ฆฃ","๐","๐ฆ","๐ฆ","๐ช","๐ซ",
|
|
78
|
+
"๐ฆ","๐ฆ","๐ฆฌ","๐","๐","๐","๐","๐",
|
|
79
|
+
"๐","๐","๐ฆ","๐","๐ฆ","๐ซ","๐","๐ฉ",
|
|
80
|
+
"๐ฆฎ","๐โ๐ฆบ","๐","๐โโฌ","๐ชถ","๐","๐ฆ","๐ฆค",
|
|
81
|
+
"๐ฆ","๐ฆ","๐ฆข","๐ชฟ","๐ฆฉ","๐๏ธ","๐","๐ฆ",
|
|
82
|
+
"๐ฆจ","๐ฆก","๐ฆซ","๐ฆฆ","๐ฆฅ","๐","๐","๐ฟ๏ธ",
|
|
83
|
+
"๐ฆ","๐ต","๐","๐ฒ","๐ณ","๐ด","๐ชต","๐ฑ",
|
|
84
|
+
"๐ฟ","โ๏ธ","๐","๐","๐ชด","๐","๐","๐",
|
|
85
|
+
"๐","๐ชบ","๐ชน","๐","๐พ","๐","๐ท","๐น",
|
|
86
|
+
"๐ฅ","๐ชป","๐บ","๐ธ","๐ผ","๐ป","๐","๐",
|
|
87
|
+
"๐","๐","๐","๐","๐","๐","๐","๐",
|
|
88
|
+
"๐","๐","๐","๐","๐","๐","๐","๐ช",
|
|
89
|
+
"๐ซ","โญ","๐","โจ","โก","โ๏ธ","๐ฅ","๐ฅ",
|
|
90
|
+
"๐ช๏ธ","๐","โ๏ธ","๐ค๏ธ","โ
","๐ฅ๏ธ","โ๏ธ","๐ฆ๏ธ",
|
|
91
|
+
"๐ง๏ธ","โ๏ธ","๐ฉ๏ธ","โ๏ธ","โ๏ธ","โ","๐ฌ๏ธ","๐จ",
|
|
92
|
+
"๐ง","๐ฆ","๐ซง","โ","โ๏ธ","๐","๐ซ๏ธ",
|
|
93
|
+
]},
|
|
94
|
+
{ id: "food", icon: "๐", label: "Food & Drink", emojis: [
|
|
95
|
+
"๐","๐","๐","๐","๐","๐","๐","๐ฅญ",
|
|
96
|
+
"๐","๐","๐","๐","๐","๐","๐ซ","๐ฅ",
|
|
97
|
+
"๐
","๐ซ","๐ฅฅ","๐ฅ","๐","๐ฅ","๐ฅ","๐ฝ",
|
|
98
|
+
"๐ถ๏ธ","๐ซ","๐ฅ","๐ฅฌ","๐ฅฆ","๐ง","๐ง
","๐ฅ",
|
|
99
|
+
"๐ซ","๐ฐ","๐ซ","๐ซ","๐","๐ฅ","๐ฅ","๐ซ",
|
|
100
|
+
"๐ฅจ","๐ฅฏ","๐ฅ","๐ง","๐ง","๐","๐","๐ฅฉ",
|
|
101
|
+
"๐ฅ","๐","๐","๐","๐ญ","๐ฅช","๐ฎ","๐ฏ",
|
|
102
|
+
"๐ซ","๐ฅ","๐ง","๐ฅ","๐ณ","๐ฅ","๐ฒ","๐ซ",
|
|
103
|
+
"๐ฅฃ","๐ฅ","๐ฟ","๐ง","๐ง","๐ฅซ","๐ฑ","๐",
|
|
104
|
+
"๐","๐","๐","๐","๐","๐ ","๐ข","๐ฃ",
|
|
105
|
+
"๐ค","๐ฅ","๐ฅฎ","๐ก","๐ฅ","๐ฅ ","๐ฅก","๐ฆ",
|
|
106
|
+
"๐ฆ","๐ฆ","๐ฆ","๐ฆช","๐ฆ","๐ง","๐จ","๐ฉ",
|
|
107
|
+
"๐ช","๐","๐ฐ","๐ง","๐ฅง","๐ซ","๐ฌ","๐ญ",
|
|
108
|
+
"๐ฎ","๐ฏ","๐ผ","๐ฅ","โ","๐ซ","๐ต","๐ถ",
|
|
109
|
+
"๐พ","๐ท","๐ธ","๐น","๐บ","๐ป","๐ฅ","๐ฅ",
|
|
110
|
+
"๐ซ","๐ฅค","๐ง","๐ง","๐ง","๐ง",
|
|
111
|
+
]},
|
|
112
|
+
{ id: "activity", icon: "โฝ", label: "Activity", emojis: [
|
|
113
|
+
"โฝ","๐","๐","โพ","๐ฅ","๐พ","๐","๐",
|
|
114
|
+
"๐ฅ","๐ฑ","๐ช","๐","๐ธ","๐","๐","๐ฅ",
|
|
115
|
+
"๐","๐ช","๐ฅ
","โณ","๐ช","๐","๐น","๐ฃ",
|
|
116
|
+
"๐คฟ","๐ฅ","๐ฅ","๐ฝ","๐น","๐ผ","๐ท","โธ๏ธ",
|
|
117
|
+
"๐ฅ","๐ฟ","โท๏ธ","๐","๐ช","๐๏ธ","๐คธ","๐คบ",
|
|
118
|
+
"โน๏ธ","๐คพ","๐๏ธ","๐","๐ง","๐","๐","๐คฝ",
|
|
119
|
+
"๐ฃ","๐ง","๐ต","๐ด","๐ช","๐คน","๐ญ","๐จ",
|
|
120
|
+
"๐ฌ","๐ค","๐ง","๐ผ","๐น","๐ฅ","๐ช","๐ท",
|
|
121
|
+
"๐บ","๐ช","๐ธ","๐ช","๐ป","๐ช","๐ฒ","โ๏ธ",
|
|
122
|
+
"๐ฏ","๐ณ","๐ฎ","๐น๏ธ","๐งฉ","๐ชฉ",
|
|
123
|
+
]},
|
|
124
|
+
{ id: "travel", icon: "๐", label: "Travel & Places", emojis: [
|
|
125
|
+
"๐","๐","๐","๐","๐","๐๏ธ","๐","๐",
|
|
126
|
+
"๐","๐","๐ป","๐","๐","๐","๐ต","๐๏ธ",
|
|
127
|
+
"๐บ","๐ฒ","๐ด","๐น","๐","๐ฃ๏ธ","๐ค๏ธ","โฝ",
|
|
128
|
+
"๐","๐จ","๐ฅ","๐ฆ","๐","๐ง","โ","๐",
|
|
129
|
+
"โต","๐ถ","๐ค","๐ณ๏ธ","โด๏ธ","๐ฅ๏ธ","๐ข","โ๏ธ",
|
|
130
|
+
"๐ฉ๏ธ","๐ซ","๐ฌ","๐ช","๐บ","๐","๐","๐ ",
|
|
131
|
+
"๐ก","๐ฐ๏ธ","๐","๐ธ","๐ ","๐ก","๐๏ธ","๐๏ธ",
|
|
132
|
+
"๐๏ธ","๐ญ","๐ข","๐ฌ","๐ฃ","๐ค","๐ฅ","๐ฆ",
|
|
133
|
+
"๐จ","๐ช","๐ซ","๐ฉ","๐","๐๏ธ","โช","๐",
|
|
134
|
+
"๐","๐","โฉ๏ธ","๐","โฒ","โบ","๐","๐",
|
|
135
|
+
"๐๏ธ","๐","๐
","๐","๐","๐","๐ผ","๐ฝ",
|
|
136
|
+
"๐ป","๐๏ธ","๐ ","๐ก","๐ข","๐๏ธ","๐๏ธ","๐๏ธ",
|
|
137
|
+
"๐","โฐ๏ธ","๐บ๏ธ","๐งญ","๐๏ธ",
|
|
138
|
+
]},
|
|
139
|
+
{ id: "objects", icon: "๐ก", label: "Objects", emojis: [
|
|
140
|
+
"โ","๐ฑ","๐ฒ","๐ป","โจ๏ธ","๐ฅ๏ธ","๐จ๏ธ","๐ฑ๏ธ",
|
|
141
|
+
"๐ฒ๏ธ","๐น๏ธ","๐๏ธ","๐ฝ","๐พ","๐ฟ","๐","๐ผ",
|
|
142
|
+
"๐ท","๐ธ","๐น","๐ฅ","๐ฝ๏ธ","๐๏ธ","๐","โ๏ธ",
|
|
143
|
+
"๐","๐ ","๐บ","๐ป","๐๏ธ","๐๏ธ","๐๏ธ","๐งญ",
|
|
144
|
+
"โฑ๏ธ","โฒ๏ธ","โฐ","๐ฐ๏ธ","โ","โณ","๐ก","๐",
|
|
145
|
+
"๐ชซ","๐","๐ก","๐ฆ","๐ฏ๏ธ","๐ช","๐งฏ","๐ข๏ธ",
|
|
146
|
+
"๐๏ธ","๐ฐ","๐ด","๐ต","๐ถ","๐ท","๐ช","๐ธ",
|
|
147
|
+
"๐ณ","๐งพ","๐น","โ๏ธ","๐ง","๐จ","๐ฉ","๐ค",
|
|
148
|
+
"๐ฅ","๐ฆ","๐ซ","๐ฌ","๐ญ","๐ฎ","๐ณ๏ธ","โ๏ธ",
|
|
149
|
+
"โ๏ธ","๐๏ธ","๐๏ธ","๐๏ธ","๐๏ธ","๐","๐ผ","๐",
|
|
150
|
+
"๐","๐๏ธ","๐
","๐","๐๏ธ","๐๏ธ","๐","๐",
|
|
151
|
+
"๐","๐","๐","๐","๐","๐","๐๏ธ","๐",
|
|
152
|
+
"๐","โ๏ธ","๐๏ธ","๐๏ธ","๐๏ธ","๐","๐","๐",
|
|
153
|
+
"๐","๐","๐๏ธ","๐จ","๐ช","โ๏ธ","โ๏ธ","๐ ๏ธ",
|
|
154
|
+
"๐ก๏ธ","โ๏ธ","๐ฃ","๐ช","๐น","๐ก๏ธ","๐ช","๐ง",
|
|
155
|
+
"๐ช","๐ฉ","โ๏ธ","๐๏ธ","โ๏ธ","๐ฆฏ","๐","โ๏ธ",
|
|
156
|
+
"๐ช","๐งฐ","๐งฒ","๐ช","โ๏ธ","๐งช","๐งซ","๐งฌ",
|
|
157
|
+
"๐ฌ","๐ญ","๐ก","๐","๐ฉธ","๐","๐ฉน","๐ฉผ",
|
|
158
|
+
"๐ฉบ","๐ฉป","๐ช","๐","๐ช","๐ช","๐๏ธ","๐๏ธ",
|
|
159
|
+
"๐ช","๐ฝ","๐ช ","๐ฟ","๐","๐ชค","๐ช","๐งด",
|
|
160
|
+
"๐งท","๐งน","๐งบ","๐งป","๐ชฃ","๐งผ","๐ซง","๐ชฅ",
|
|
161
|
+
"๐งฝ","๐งฏ","๐","๐ฌ","โฐ๏ธ","๐ชฆ","โฑ๏ธ","๐งฟ",
|
|
162
|
+
"๐ชฌ","๐ฟ","๐ชง","๐ชช",
|
|
163
|
+
]},
|
|
164
|
+
{ id: "symbols", icon: "โค๏ธ", label: "Symbols", emojis: [
|
|
165
|
+
"โค๏ธ","๐งก","๐","๐","๐","๐","๐ค","๐ค",
|
|
166
|
+
"๐ค","๐","โค๏ธโ๐ฅ","โค๏ธโ๐ฉน","โฃ๏ธ","๐","๐","๐",
|
|
167
|
+
"๐","๐","๐","๐","๐","โฎ๏ธ","โ๏ธ","โช๏ธ",
|
|
168
|
+
"๐๏ธ","โธ๏ธ","๐ชฏ","โก๏ธ","๐ฏ","๐","โฏ๏ธ","โฆ๏ธ",
|
|
169
|
+
"๐","โ","โ","โ","โ","โ","โ","โ",
|
|
170
|
+
"โ","โ","โ","โ","โ","โ","๐","โ๏ธ",
|
|
171
|
+
"๐","โข๏ธ","โฃ๏ธ","๐ด","๐ณ","๐ถ","๐","๐ธ",
|
|
172
|
+
"๐บ","๐ท๏ธ","โด๏ธ","๐","๐ฎ","๐","ใ๏ธ","ใ๏ธ",
|
|
173
|
+
"๐ด","๐ต","๐น","๐ฒ","๐
ฐ๏ธ","๐
ฑ๏ธ","๐","๐",
|
|
174
|
+
"๐
พ๏ธ","๐","โ","โญ","๐","โ","๐","๐ซ",
|
|
175
|
+
"๐ฏ","๐ข","โจ๏ธ","๐ท","๐ฏ","๐ณ","๐ฑ","๐",
|
|
176
|
+
"๐ต","๐ญ","โ","โ","โ","โ","โผ๏ธ","โ๏ธ",
|
|
177
|
+
"๐
","๐","ใฝ๏ธ","โ ๏ธ","๐ธ","๐ฑ","โ๏ธ","๐ฐ",
|
|
178
|
+
"โป๏ธ","โ
","๐ฏ","๐น","โ๏ธ","โณ๏ธ","โ","๐",
|
|
179
|
+
"๐ ","โ๏ธ","๐","๐ค","๐ง","๐พ","โฟ","๐
ฟ๏ธ",
|
|
180
|
+
"๐","๐ณ","๐๏ธ","๐","๐","๐","๐
","๐น",
|
|
181
|
+
"๐บ","๐ผ","โง๏ธ","๐ป","๐ฎ","๐ฆ","๐ถ","๐",
|
|
182
|
+
"๐ฃ","โน๏ธ","๐ค","๐ก","๐ ","๐","๐","๐",
|
|
183
|
+
"๐","๐","๐","0๏ธโฃ","1๏ธโฃ","2๏ธโฃ","3๏ธโฃ","4๏ธโฃ",
|
|
184
|
+
"5๏ธโฃ","6๏ธโฃ","7๏ธโฃ","8๏ธโฃ","9๏ธโฃ","๐","๐ข","#๏ธโฃ",
|
|
185
|
+
"*๏ธโฃ","โ๏ธ","โถ๏ธ","โธ๏ธ","โฏ๏ธ","โน๏ธ","โบ๏ธ","โญ๏ธ",
|
|
186
|
+
"โฎ๏ธ","โฉ","โช","โซ","โฌ","โ๏ธ","๐ผ","๐ฝ",
|
|
187
|
+
"โก๏ธ","โฌ
๏ธ","โฌ๏ธ","โฌ๏ธ","โ๏ธ","โ๏ธ","โ๏ธ","โ๏ธ",
|
|
188
|
+
"โ๏ธ","โ๏ธ","โฉ๏ธ","โช๏ธ","โคด๏ธ","โคต๏ธ","๐","๐",
|
|
189
|
+
"๐","๐","๐","๐ต","๐ถ","โ๏ธ","โ","โ",
|
|
190
|
+
"โ","๐ฐ","โพ๏ธ","๐ฒ","๐ฑ","โข๏ธ","ยฉ๏ธ","ยฎ๏ธ",
|
|
191
|
+
"ใฐ๏ธ","โฐ","โฟ","๐","๐","๐","๐","๐",
|
|
192
|
+
"โ๏ธ","โ๏ธ","๐","๐ด","๐ ","๐ก","๐ข","๐ต",
|
|
193
|
+
"๐ฃ","โซ","โช","๐ค","๐บ","๐ป","๐ธ","๐น",
|
|
194
|
+
"๐ถ","๐ท","๐ณ","๐ฒ","โช๏ธ","โซ๏ธ","โพ","โฝ",
|
|
195
|
+
"โผ๏ธ","โป๏ธ","๐ฅ","๐ง","๐จ","๐ฉ","๐ฆ","๐ช",
|
|
196
|
+
"โฌ","โฌ","๐ซ","๐","๐","๐","๐","๐",
|
|
197
|
+
"๐","๐ฃ","๐ข","๐๏ธโ๐จ๏ธ","๐ฌ","๐ญ","๐ฏ๏ธ","โ ๏ธ",
|
|
198
|
+
"โฃ๏ธ","โฅ๏ธ","โฆ๏ธ","๐","๐ด","๐","๐","๐",
|
|
199
|
+
"๐","๐","๐","๐","๐","๐","๐","๐","๐","๐",
|
|
200
|
+
]},
|
|
201
|
+
{ id: "flags", icon: "๐", label: "Flags", emojis: [
|
|
202
|
+
"๐","๐ฉ","๐","๐ด","๐ณ๏ธ","๐ณ๏ธโ๐","๐ณ๏ธโโง๏ธ","๐ดโโ ๏ธ",
|
|
203
|
+
"๐ฆ๐จ","๐ฆ๐ฉ","๐ฆ๐ช","๐ฆ๐ซ","๐ฆ๐ฌ","๐ฆ๐ฎ","๐ฆ๐ฑ","๐ฆ๐ฒ",
|
|
204
|
+
"๐ฆ๐ด","๐ฆ๐ถ","๐ฆ๐ท","๐ฆ๐ธ","๐ฆ๐น","๐ฆ๐บ","๐ฆ๐ผ","๐ฆ๐ฝ",
|
|
205
|
+
"๐ฆ๐ฟ","๐ง๐ฆ","๐ง๐ง","๐ง๐ฉ","๐ง๐ช","๐ง๐ซ","๐ง๐ฌ","๐ง๐ญ",
|
|
206
|
+
"๐ง๐ฎ","๐ง๐ฏ","๐ง๐ฑ","๐ง๐ฒ","๐ง๐ณ","๐ง๐ด","๐ง๐ถ","๐ง๐ท",
|
|
207
|
+
"๐ง๐ธ","๐ง๐น","๐ง๐ป","๐ง๐ผ","๐ง๐พ","๐ง๐ฟ","๐จ๐ฆ","๐จ๐จ",
|
|
208
|
+
"๐จ๐ฉ","๐จ๐ซ","๐จ๐ฌ","๐จ๐ญ","๐จ๐ฎ","๐จ๐ฐ","๐จ๐ฑ","๐จ๐ฒ",
|
|
209
|
+
"๐จ๐ณ","๐จ๐ด","๐จ๐ต","๐จ๐ท","๐จ๐บ","๐จ๐ป","๐จ๐ผ","๐จ๐ฝ",
|
|
210
|
+
"๐จ๐พ","๐จ๐ฟ","๐ฉ๐ช","๐ฉ๐ฌ","๐ฉ๐ฏ","๐ฉ๐ฐ","๐ฉ๐ฒ","๐ฉ๐ด",
|
|
211
|
+
"๐ฉ๐ฟ","๐ช๐ฆ","๐ช๐จ","๐ช๐ช","๐ช๐ฌ","๐ช๐ญ","๐ช๐ท","๐ช๐ธ",
|
|
212
|
+
"๐ช๐น","๐ช๐บ","๐ซ๐ฎ","๐ซ๐ฏ","๐ซ๐ฐ","๐ซ๐ฒ","๐ซ๐ด","๐ซ๐ท",
|
|
213
|
+
"๐ฌ๐ฆ","๐ฌ๐ง","๐ฌ๐ฉ","๐ฌ๐ช","๐ฌ๐ซ","๐ฌ๐ฌ","๐ฌ๐ญ","๐ฌ๐ฎ",
|
|
214
|
+
"๐ฌ๐ฑ","๐ฌ๐ฒ","๐ฌ๐ณ","๐ฌ๐ต","๐ฌ๐ถ","๐ฌ๐ท","๐ฌ๐ธ","๐ฌ๐น",
|
|
215
|
+
"๐ฌ๐บ","๐ฌ๐ผ","๐ฌ๐พ","๐ญ๐ฐ","๐ญ๐ฒ","๐ญ๐ณ","๐ญ๐ท","๐ญ๐น",
|
|
216
|
+
"๐ญ๐บ","๐ฎ๐จ","๐ฎ๐ฉ","๐ฎ๐ช","๐ฎ๐ฑ","๐ฎ๐ฒ","๐ฎ๐ณ","๐ฎ๐ด",
|
|
217
|
+
"๐ฎ๐ถ","๐ฎ๐ท","๐ฎ๐ธ","๐ฎ๐น","๐ฏ๐ช","๐ฏ๐ฒ","๐ฏ๐ด","๐ฏ๐ต",
|
|
218
|
+
"๐ฐ๐ช","๐ฐ๐ฌ","๐ฐ๐ญ","๐ฐ๐ฎ","๐ฐ๐ฒ","๐ฐ๐ณ","๐ฐ๐ต","๐ฐ๐ท",
|
|
219
|
+
"๐ฐ๐ผ","๐ฐ๐พ","๐ฐ๐ฟ","๐ฑ๐ฆ","๐ฑ๐ง","๐ฑ๐จ","๐ฑ๐ฎ","๐ฑ๐ฐ",
|
|
220
|
+
"๐ฑ๐ท","๐ฑ๐ธ","๐ฑ๐น","๐ฑ๐บ","๐ฑ๐ป","๐ฑ๐พ","๐ฒ๐ฆ","๐ฒ๐จ",
|
|
221
|
+
"๐ฒ๐ฉ","๐ฒ๐ช","๐ฒ๐ซ","๐ฒ๐ฌ","๐ฒ๐ญ","๐ฒ๐ฐ","๐ฒ๐ฑ","๐ฒ๐ฒ",
|
|
222
|
+
"๐ฒ๐ณ","๐ฒ๐ด","๐ฒ๐ต","๐ฒ๐ถ","๐ฒ๐ท","๐ฒ๐ธ","๐ฒ๐น","๐ฒ๐บ",
|
|
223
|
+
"๐ฒ๐ป","๐ฒ๐ผ","๐ฒ๐ฝ","๐ฒ๐พ","๐ฒ๐ฟ","๐ณ๐ฆ","๐ณ๐จ","๐ณ๐ช",
|
|
224
|
+
"๐ณ๐ซ","๐ณ๐ฌ","๐ณ๐ฎ","๐ณ๐ฑ","๐ณ๐ด","๐ณ๐ต","๐ณ๐ท","๐ณ๐บ",
|
|
225
|
+
"๐ณ๐ฟ","๐ด๐ฒ","๐ต๐ฆ","๐ต๐ช","๐ต๐ซ","๐ต๐ฌ","๐ต๐ญ","๐ต๐ฐ",
|
|
226
|
+
"๐ต๐ฑ","๐ต๐ฒ","๐ต๐ณ","๐ต๐ท","๐ต๐ธ","๐ต๐น","๐ต๐ผ","๐ต๐พ",
|
|
227
|
+
"๐ถ๐ฆ","๐ท๐ช","๐ท๐ด","๐ท๐ธ","๐ท๐บ","๐ท๐ผ","๐ธ๐ฆ","๐ธ๐ง",
|
|
228
|
+
"๐ธ๐จ","๐ธ๐ฉ","๐ธ๐ช","๐ธ๐ฌ","๐ธ๐ญ","๐ธ๐ฎ","๐ธ๐ฏ","๐ธ๐ฐ",
|
|
229
|
+
"๐ธ๐ฑ","๐ธ๐ฒ","๐ธ๐ณ","๐ธ๐ด","๐ธ๐ท","๐ธ๐ธ","๐ธ๐น","๐ธ๐ป",
|
|
230
|
+
"๐ธ๐ฝ","๐ธ๐พ","๐ธ๐ฟ","๐น๐ฆ","๐น๐จ","๐น๐ฉ","๐น๐ซ","๐น๐ฌ",
|
|
231
|
+
"๐น๐ญ","๐น๐ฏ","๐น๐ฐ","๐น๐ฑ","๐น๐ฒ","๐น๐ณ","๐น๐ด","๐น๐ท",
|
|
232
|
+
"๐น๐น","๐น๐ป","๐น๐ผ","๐น๐ฟ","๐บ๐ฆ","๐บ๐ฌ","๐บ๐ฒ","๐บ๐ณ",
|
|
233
|
+
"๐บ๐ธ","๐บ๐พ","๐บ๐ฟ","๐ป๐ฆ","๐ป๐จ","๐ป๐ช","๐ป๐ฌ","๐ป๐ฎ",
|
|
234
|
+
"๐ป๐ณ","๐ป๐บ","๐ผ๐ซ","๐ผ๐ธ","๐ฝ๐ฐ","๐พ๐ช","๐พ๐น","๐ฟ๐ฆ",
|
|
235
|
+
"๐ฟ๐ฒ","๐ฟ๐ผ",
|
|
236
|
+
]},
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
export function initSidebarProjects(ctx) {
|
|
240
|
+
_ctx = ctx;
|
|
241
|
+
|
|
242
|
+
// Close project ctx menu and emoji picker on document click
|
|
243
|
+
document.addEventListener("click", function () {
|
|
244
|
+
closeProjectCtxMenu();
|
|
245
|
+
closeEmojiPicker();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Initialize icon strip buttons
|
|
249
|
+
var addBtn = document.getElementById("icon-strip-add");
|
|
250
|
+
if (addBtn) {
|
|
251
|
+
addBtn.addEventListener("click", function () {
|
|
252
|
+
if (_ctx.openAddProjectModal) {
|
|
253
|
+
_ctx.openAddProjectModal();
|
|
254
|
+
} else {
|
|
255
|
+
var modal = _ctx.$("add-project-modal");
|
|
256
|
+
if (modal) modal.classList.remove("hidden");
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
addBtn.addEventListener("mouseenter", function () { _ctx.showIconTooltip(addBtn, "Add project"); });
|
|
260
|
+
addBtn.addEventListener("mouseleave", _ctx.hideIconTooltip);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
var exploreBtn = document.getElementById("icon-strip-explore");
|
|
264
|
+
if (exploreBtn) {
|
|
265
|
+
exploreBtn.addEventListener("click", function () {
|
|
266
|
+
var fileBrowserBtn = _ctx.$("file-browser-btn");
|
|
267
|
+
if (fileBrowserBtn) fileBrowserBtn.click();
|
|
268
|
+
});
|
|
269
|
+
exploreBtn.addEventListener("mouseenter", function () { _ctx.showIconTooltip(exploreBtn, "File browser"); });
|
|
270
|
+
exploreBtn.addEventListener("mouseleave", _ctx.hideIconTooltip);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Tooltip + click for home icon
|
|
274
|
+
var homeIcon = document.querySelector(".icon-strip-home");
|
|
275
|
+
if (homeIcon) {
|
|
276
|
+
homeIcon.addEventListener("mouseenter", function () { _ctx.showIconTooltip(homeIcon, "Clay"); });
|
|
277
|
+
homeIcon.addEventListener("mouseleave", _ctx.hideIconTooltip);
|
|
278
|
+
homeIcon.addEventListener("click", function (e) {
|
|
279
|
+
e.preventDefault();
|
|
280
|
+
if (_ctx.showHomeHub) _ctx.showHomeHub();
|
|
281
|
+
});
|
|
282
|
+
homeIcon.style.cursor = "pointer";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Chevron dropdown on project name
|
|
286
|
+
var dropdownBtn = document.getElementById("title-bar-project-dropdown");
|
|
287
|
+
if (dropdownBtn) {
|
|
288
|
+
dropdownBtn.addEventListener("click", function (e) {
|
|
289
|
+
e.stopPropagation();
|
|
290
|
+
var current = null;
|
|
291
|
+
for (var i = 0; i < cachedProjectList.length; i++) {
|
|
292
|
+
if (cachedProjectList[i].slug === cachedCurrentSlug) {
|
|
293
|
+
current = cachedProjectList[i];
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (!current) return;
|
|
298
|
+
|
|
299
|
+
if (projectCtxMenu) {
|
|
300
|
+
closeProjectCtxMenu();
|
|
301
|
+
dropdownBtn.classList.remove("open");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
dropdownBtn.classList.add("open");
|
|
305
|
+
showProjectCtxMenu(dropdownBtn, current.slug, current.name, current.icon, "below");
|
|
306
|
+
var observer = new MutationObserver(function () {
|
|
307
|
+
if (!projectCtxMenu) {
|
|
308
|
+
dropdownBtn.classList.remove("open");
|
|
309
|
+
observer.disconnect();
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
observer.observe(document.body, { childList: true });
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
renderIconStrip: renderIconStrip,
|
|
318
|
+
renderProjectList: renderProjectList,
|
|
319
|
+
updateBadge: updateProjectBadge,
|
|
320
|
+
getEmojiCategories: getEmojiCategories
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// --- Getters for cached state (used by mobile sheet in sidebar.js) ---
|
|
325
|
+
export function getCachedProjectList() { return cachedProjectList; }
|
|
326
|
+
export function getCachedCurrentSlug() { return cachedCurrentSlug; }
|
|
327
|
+
|
|
328
|
+
function getProjectAbbrev(name) {
|
|
329
|
+
if (!name) return "?";
|
|
330
|
+
var words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
|
|
331
|
+
if (words.length >= 2) {
|
|
332
|
+
return (words[0][0] + words[1][0]).toUpperCase();
|
|
333
|
+
}
|
|
334
|
+
return name.substring(0, 2).toUpperCase();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export { getProjectAbbrev };
|
|
338
|
+
|
|
339
|
+
// --- Project Access Popover ---
|
|
340
|
+
|
|
341
|
+
function closeAccessOnOutside(e) {
|
|
342
|
+
if (projectAccessPopover && !projectAccessPopover.contains(e.target)) closeProjectAccessPopover();
|
|
343
|
+
}
|
|
344
|
+
function closeAccessOnEscape(e) {
|
|
345
|
+
if (e.key === "Escape") closeProjectAccessPopover();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function closeProjectAccessPopover() {
|
|
349
|
+
if (projectAccessPopover) {
|
|
350
|
+
projectAccessPopover.remove();
|
|
351
|
+
projectAccessPopover = null;
|
|
352
|
+
document.removeEventListener("click", closeAccessOnOutside);
|
|
353
|
+
document.removeEventListener("keydown", closeAccessOnEscape);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function showProjectAccessPopover(anchorEl, slug) {
|
|
358
|
+
closeProjectAccessPopover();
|
|
359
|
+
|
|
360
|
+
var popover = document.createElement("div");
|
|
361
|
+
popover.className = "project-access-popover";
|
|
362
|
+
popover.innerHTML = '<div class="project-access-loading">Loading...</div>';
|
|
363
|
+
popover.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
364
|
+
document.body.appendChild(popover);
|
|
365
|
+
projectAccessPopover = popover;
|
|
366
|
+
|
|
367
|
+
requestAnimationFrame(function () {
|
|
368
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
369
|
+
popover.style.position = "fixed";
|
|
370
|
+
popover.style.left = (rect.right + 8) + "px";
|
|
371
|
+
popover.style.top = rect.top + "px";
|
|
372
|
+
popover.style.zIndex = "9999";
|
|
373
|
+
var popRect = popover.getBoundingClientRect();
|
|
374
|
+
if (popRect.right > window.innerWidth - 8) {
|
|
375
|
+
popover.style.left = (rect.left - popRect.width - 8) + "px";
|
|
376
|
+
}
|
|
377
|
+
if (popRect.bottom > window.innerHeight - 8) {
|
|
378
|
+
popover.style.top = (window.innerHeight - popRect.height - 8) + "px";
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
setTimeout(function () {
|
|
383
|
+
document.addEventListener("click", closeAccessOnOutside);
|
|
384
|
+
document.addEventListener("keydown", closeAccessOnEscape);
|
|
385
|
+
}, 0);
|
|
386
|
+
|
|
387
|
+
Promise.all([
|
|
388
|
+
fetch("/api/admin/projects/" + encodeURIComponent(slug) + "/access").then(function (r) { return r.json(); }),
|
|
389
|
+
fetch("/api/admin/users").then(function (r) { return r.json(); }),
|
|
390
|
+
]).then(function (results) {
|
|
391
|
+
var access = results[0];
|
|
392
|
+
var usersData = results[1];
|
|
393
|
+
if (access.error || usersData.error) {
|
|
394
|
+
popover.innerHTML = '<div class="project-access-loading">Failed to load</div>';
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
renderAccessPopover(popover, slug, access, usersData.users || []);
|
|
398
|
+
}).catch(function () {
|
|
399
|
+
popover.innerHTML = '<div class="project-access-loading">Failed to load</div>';
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function renderAccessPopover(popover, slug, access, allUsers) {
|
|
404
|
+
var visibility = access.visibility || "public";
|
|
405
|
+
var allowedUsers = access.allowedUsers || [];
|
|
406
|
+
var ownerId = access.ownerId;
|
|
407
|
+
|
|
408
|
+
var selectableUsers = allUsers.filter(function (u) { return u.id !== ownerId; });
|
|
409
|
+
|
|
410
|
+
var html = '';
|
|
411
|
+
html += '<div class="project-access-header">';
|
|
412
|
+
html += '<span class="project-access-title">Project Access</span>';
|
|
413
|
+
html += '<button class="project-access-close">×</button>';
|
|
414
|
+
html += '</div>';
|
|
415
|
+
|
|
416
|
+
html += '<div class="project-access-section">';
|
|
417
|
+
html += '<label class="project-access-label">Visibility</label>';
|
|
418
|
+
html += '<div class="project-access-vis-row">';
|
|
419
|
+
html += '<button class="project-access-vis-btn' + (visibility === "private" ? ' active' : '') + '" data-vis="private">';
|
|
420
|
+
html += iconHtml("lock") + ' Private';
|
|
421
|
+
html += '</button>';
|
|
422
|
+
html += '<button class="project-access-vis-btn' + (visibility === "public" ? ' active' : '') + '" data-vis="public">';
|
|
423
|
+
html += iconHtml("globe") + ' Public';
|
|
424
|
+
html += '</button>';
|
|
425
|
+
html += '</div>';
|
|
426
|
+
html += '</div>';
|
|
427
|
+
|
|
428
|
+
html += '<div class="project-access-section project-access-users-section"' + (visibility !== "private" ? ' style="display:none"' : '') + '>';
|
|
429
|
+
html += '<label class="project-access-label">Allowed Users</label>';
|
|
430
|
+
html += '<div class="project-access-user-list">';
|
|
431
|
+
for (var i = 0; i < selectableUsers.length; i++) {
|
|
432
|
+
var u = selectableUsers[i];
|
|
433
|
+
var checked = allowedUsers.indexOf(u.id) !== -1 ? " checked" : "";
|
|
434
|
+
html += '<label class="project-access-user-item">';
|
|
435
|
+
html += '<input type="checkbox" data-uid="' + u.id + '"' + checked + '>';
|
|
436
|
+
html += '<span>' + escapeHtml(u.displayName || u.username || u.id) + '</span>';
|
|
437
|
+
html += '</label>';
|
|
438
|
+
}
|
|
439
|
+
if (selectableUsers.length === 0) {
|
|
440
|
+
html += '<div class="project-access-empty">No other users</div>';
|
|
441
|
+
}
|
|
442
|
+
html += '</div>';
|
|
443
|
+
html += '</div>';
|
|
444
|
+
|
|
445
|
+
popover.innerHTML = html;
|
|
446
|
+
refreshIcons();
|
|
447
|
+
|
|
448
|
+
popover.querySelector(".project-access-close").addEventListener("click", function () {
|
|
449
|
+
closeProjectAccessPopover();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
popover.querySelectorAll(".project-access-vis-btn").forEach(function (btn) {
|
|
453
|
+
btn.addEventListener("click", function () {
|
|
454
|
+
var newVis = btn.dataset.vis;
|
|
455
|
+
popover.querySelectorAll(".project-access-vis-btn").forEach(function (b) { b.classList.remove("active"); });
|
|
456
|
+
btn.classList.add("active");
|
|
457
|
+
var usersSection = popover.querySelector(".project-access-users-section");
|
|
458
|
+
if (usersSection) usersSection.style.display = newVis === "private" ? "" : "none";
|
|
459
|
+
fetch("/api/admin/projects/" + encodeURIComponent(slug) + "/visibility", {
|
|
460
|
+
method: "PUT",
|
|
461
|
+
headers: { "Content-Type": "application/json" },
|
|
462
|
+
body: JSON.stringify({ visibility: newVis }),
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
popover.querySelectorAll('.project-access-user-item input[type="checkbox"]').forEach(function (cb) {
|
|
468
|
+
cb.addEventListener("change", function () {
|
|
469
|
+
var selected = [];
|
|
470
|
+
popover.querySelectorAll('.project-access-user-item input[type="checkbox"]:checked').forEach(function (c) {
|
|
471
|
+
selected.push(c.dataset.uid);
|
|
472
|
+
});
|
|
473
|
+
fetch("/api/admin/projects/" + encodeURIComponent(slug) + "/users", {
|
|
474
|
+
method: "PUT",
|
|
475
|
+
headers: { "Content-Type": "application/json" },
|
|
476
|
+
body: JSON.stringify({ allowedUsers: selected }),
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// --- Project context menu ---
|
|
483
|
+
|
|
484
|
+
export function closeProjectCtxMenu() {
|
|
485
|
+
if (projectCtxMenu) {
|
|
486
|
+
projectCtxMenu.remove();
|
|
487
|
+
projectCtxMenu = null;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function showIconCtxMenu(anchorEl, slug, name) {
|
|
492
|
+
closeProjectCtxMenu();
|
|
493
|
+
if (_ctx.closeUserCtxMenu) _ctx.closeUserCtxMenu();
|
|
494
|
+
closeEmojiPicker();
|
|
495
|
+
|
|
496
|
+
var menu = document.createElement("div");
|
|
497
|
+
menu.className = "project-ctx-menu";
|
|
498
|
+
|
|
499
|
+
var isWorktree = slug.indexOf("--") !== -1;
|
|
500
|
+
|
|
501
|
+
if (isWorktree) {
|
|
502
|
+
var removeWtItem = document.createElement("button");
|
|
503
|
+
removeWtItem.className = "project-ctx-item project-ctx-delete";
|
|
504
|
+
removeWtItem.innerHTML = iconHtml("trash-2") + " <span>Remove Worktree</span>";
|
|
505
|
+
removeWtItem.addEventListener("click", function (e) {
|
|
506
|
+
e.stopPropagation();
|
|
507
|
+
closeProjectCtxMenu();
|
|
508
|
+
if (_ctx.ws && _ctx.connected) {
|
|
509
|
+
_ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name || slug }));
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
menu.appendChild(removeWtItem);
|
|
513
|
+
} else {
|
|
514
|
+
var iconItem = document.createElement("button");
|
|
515
|
+
iconItem.className = "project-ctx-item";
|
|
516
|
+
iconItem.innerHTML = iconHtml("smile") + " <span>Set Icon</span>";
|
|
517
|
+
iconItem.addEventListener("click", function (e) {
|
|
518
|
+
e.stopPropagation();
|
|
519
|
+
closeProjectCtxMenu();
|
|
520
|
+
showEmojiPicker(slug, anchorEl);
|
|
521
|
+
});
|
|
522
|
+
menu.appendChild(iconItem);
|
|
523
|
+
|
|
524
|
+
var wtItem = document.createElement("button");
|
|
525
|
+
wtItem.className = "project-ctx-item";
|
|
526
|
+
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
527
|
+
wtItem.addEventListener("click", function (e) {
|
|
528
|
+
e.stopPropagation();
|
|
529
|
+
closeProjectCtxMenu();
|
|
530
|
+
showWorktreeModal(slug, name || slug);
|
|
531
|
+
});
|
|
532
|
+
menu.appendChild(wtItem);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
document.body.appendChild(menu);
|
|
536
|
+
projectCtxMenu = menu;
|
|
537
|
+
refreshIcons();
|
|
538
|
+
|
|
539
|
+
requestAnimationFrame(function () {
|
|
540
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
541
|
+
menu.style.position = "fixed";
|
|
542
|
+
menu.style.left = (rect.right + 6) + "px";
|
|
543
|
+
menu.style.top = rect.top + "px";
|
|
544
|
+
var menuRect = menu.getBoundingClientRect();
|
|
545
|
+
if (menuRect.right > window.innerWidth - 8) {
|
|
546
|
+
menu.style.left = (rect.left - menuRect.width - 6) + "px";
|
|
547
|
+
}
|
|
548
|
+
if (menuRect.bottom > window.innerHeight - 8) {
|
|
549
|
+
menu.style.top = (window.innerHeight - menuRect.height - 8) + "px";
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
555
|
+
closeProjectCtxMenu();
|
|
556
|
+
if (_ctx.closeUserCtxMenu) _ctx.closeUserCtxMenu();
|
|
557
|
+
closeEmojiPicker();
|
|
558
|
+
|
|
559
|
+
var menu = document.createElement("div");
|
|
560
|
+
menu.className = "project-ctx-menu";
|
|
561
|
+
|
|
562
|
+
// --- Set Icon ---
|
|
563
|
+
var iconItem = document.createElement("button");
|
|
564
|
+
iconItem.className = "project-ctx-item";
|
|
565
|
+
iconItem.innerHTML = iconHtml("smile") + " <span>Set Icon</span>";
|
|
566
|
+
iconItem.addEventListener("click", function (e) {
|
|
567
|
+
e.stopPropagation();
|
|
568
|
+
closeProjectCtxMenu();
|
|
569
|
+
showEmojiPicker(slug, anchorEl);
|
|
570
|
+
});
|
|
571
|
+
menu.appendChild(iconItem);
|
|
572
|
+
|
|
573
|
+
// --- Project Settings ---
|
|
574
|
+
if (!_ctx.permissions || _ctx.permissions.projectSettings !== false) {
|
|
575
|
+
var settingsItem = document.createElement("button");
|
|
576
|
+
settingsItem.className = "project-ctx-item";
|
|
577
|
+
settingsItem.innerHTML = iconHtml("settings") + " <span>Project Settings</span>";
|
|
578
|
+
settingsItem.addEventListener("click", function (e) {
|
|
579
|
+
e.stopPropagation();
|
|
580
|
+
closeProjectCtxMenu();
|
|
581
|
+
openProjectSettings(slug, { slug: slug, name: name, icon: icon, projectOwnerId: _ctx.projectOwnerId });
|
|
582
|
+
});
|
|
583
|
+
menu.appendChild(settingsItem);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
var sep1 = document.createElement("div");
|
|
587
|
+
sep1.className = "project-ctx-separator";
|
|
588
|
+
menu.appendChild(sep1);
|
|
589
|
+
|
|
590
|
+
// --- Share ---
|
|
591
|
+
var shareItem = document.createElement("button");
|
|
592
|
+
shareItem.className = "project-ctx-item";
|
|
593
|
+
shareItem.innerHTML = iconHtml("share") + " <span>Share</span>";
|
|
594
|
+
shareItem.addEventListener("click", function (e) {
|
|
595
|
+
e.stopPropagation();
|
|
596
|
+
closeProjectCtxMenu();
|
|
597
|
+
triggerShare();
|
|
598
|
+
});
|
|
599
|
+
menu.appendChild(shareItem);
|
|
600
|
+
|
|
601
|
+
// --- Manage Access ---
|
|
602
|
+
if (_ctx.multiUser && slug.indexOf("--") === -1) {
|
|
603
|
+
var isProjectOwner = _ctx.myUserId && _ctx.projectOwnerId && _ctx.myUserId === _ctx.projectOwnerId;
|
|
604
|
+
var isAdmin = _ctx.permissions && _ctx.permissions.projectSettings !== false;
|
|
605
|
+
if (isProjectOwner || isAdmin) {
|
|
606
|
+
var accessItem = document.createElement("button");
|
|
607
|
+
accessItem.className = "project-ctx-item";
|
|
608
|
+
accessItem.innerHTML = iconHtml("users") + " <span>Manage Access</span>";
|
|
609
|
+
accessItem.addEventListener("click", function (e) {
|
|
610
|
+
e.stopPropagation();
|
|
611
|
+
closeProjectCtxMenu();
|
|
612
|
+
showProjectAccessPopover(anchorEl, slug);
|
|
613
|
+
});
|
|
614
|
+
menu.appendChild(accessItem);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
var sep2 = document.createElement("div");
|
|
619
|
+
sep2.className = "project-ctx-separator";
|
|
620
|
+
menu.appendChild(sep2);
|
|
621
|
+
|
|
622
|
+
// --- Add Worktree ---
|
|
623
|
+
var wtItem = document.createElement("button");
|
|
624
|
+
wtItem.className = "project-ctx-item";
|
|
625
|
+
wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
|
|
626
|
+
wtItem.addEventListener("click", function (e) {
|
|
627
|
+
e.stopPropagation();
|
|
628
|
+
closeProjectCtxMenu();
|
|
629
|
+
showWorktreeModal(slug, name || slug);
|
|
630
|
+
});
|
|
631
|
+
menu.appendChild(wtItem);
|
|
632
|
+
|
|
633
|
+
if (!_ctx.permissions || _ctx.permissions.deleteProject !== false) {
|
|
634
|
+
var sep3 = document.createElement("div");
|
|
635
|
+
sep3.className = "project-ctx-separator";
|
|
636
|
+
menu.appendChild(sep3);
|
|
637
|
+
|
|
638
|
+
var deleteItem = document.createElement("button");
|
|
639
|
+
deleteItem.className = "project-ctx-item project-ctx-delete";
|
|
640
|
+
deleteItem.innerHTML = iconHtml("trash-2") + " <span>Remove Project</span>";
|
|
641
|
+
deleteItem.addEventListener("click", function (e) {
|
|
642
|
+
e.stopPropagation();
|
|
643
|
+
closeProjectCtxMenu();
|
|
644
|
+
if (_ctx.ws && _ctx.connected) {
|
|
645
|
+
_ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name }));
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
menu.appendChild(deleteItem);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
document.body.appendChild(menu);
|
|
652
|
+
projectCtxMenu = menu;
|
|
653
|
+
refreshIcons();
|
|
654
|
+
|
|
655
|
+
requestAnimationFrame(function () {
|
|
656
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
657
|
+
menu.style.position = "fixed";
|
|
658
|
+
if (position === "below") {
|
|
659
|
+
menu.style.left = rect.left + "px";
|
|
660
|
+
menu.style.top = (rect.bottom + 4) + "px";
|
|
661
|
+
} else {
|
|
662
|
+
menu.style.left = (rect.right + 6) + "px";
|
|
663
|
+
menu.style.top = rect.top + "px";
|
|
664
|
+
}
|
|
665
|
+
var menuRect = menu.getBoundingClientRect();
|
|
666
|
+
if (menuRect.right > window.innerWidth - 8) {
|
|
667
|
+
menu.style.left = (rect.left - menuRect.width - 6) + "px";
|
|
668
|
+
}
|
|
669
|
+
if (menuRect.bottom > window.innerHeight - 8) {
|
|
670
|
+
menu.style.top = (window.innerHeight - menuRect.height - 8) + "px";
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// --- Emoji picker ---
|
|
676
|
+
|
|
677
|
+
function closeEmojiPicker() {
|
|
678
|
+
if (emojiPickerEl) {
|
|
679
|
+
emojiPickerEl.remove();
|
|
680
|
+
emojiPickerEl = null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function showEmojiPicker(slug, anchorEl) {
|
|
685
|
+
closeEmojiPicker();
|
|
686
|
+
|
|
687
|
+
var picker = document.createElement("div");
|
|
688
|
+
picker.className = "emoji-picker";
|
|
689
|
+
picker.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
690
|
+
|
|
691
|
+
var header = document.createElement("div");
|
|
692
|
+
header.className = "emoji-picker-header";
|
|
693
|
+
header.textContent = "Choose Icon";
|
|
694
|
+
|
|
695
|
+
var removeBtn = document.createElement("button");
|
|
696
|
+
removeBtn.className = "emoji-picker-remove";
|
|
697
|
+
removeBtn.textContent = "Remove";
|
|
698
|
+
removeBtn.addEventListener("click", function (e) {
|
|
699
|
+
e.stopPropagation();
|
|
700
|
+
closeEmojiPicker();
|
|
701
|
+
if (_ctx.ws && _ctx.connected) {
|
|
702
|
+
_ctx.ws.send(JSON.stringify({ type: "set_project_icon", slug: slug, icon: null }));
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
header.appendChild(removeBtn);
|
|
706
|
+
picker.appendChild(header);
|
|
707
|
+
|
|
708
|
+
var tabBar = document.createElement("div");
|
|
709
|
+
tabBar.className = "emoji-picker-tabs";
|
|
710
|
+
var tabBtns = [];
|
|
711
|
+
|
|
712
|
+
for (var t = 0; t < EMOJI_CATEGORIES.length; t++) {
|
|
713
|
+
(function (cat, idx) {
|
|
714
|
+
var tab = document.createElement("button");
|
|
715
|
+
tab.className = "emoji-picker-tab" + (idx === 0 ? " active" : "");
|
|
716
|
+
tab.textContent = cat.icon;
|
|
717
|
+
tab.title = cat.label;
|
|
718
|
+
tab.addEventListener("click", function (e) {
|
|
719
|
+
e.stopPropagation();
|
|
720
|
+
switchCategory(idx);
|
|
721
|
+
});
|
|
722
|
+
tabBar.appendChild(tab);
|
|
723
|
+
tabBtns.push(tab);
|
|
724
|
+
})(EMOJI_CATEGORIES[t], t);
|
|
725
|
+
}
|
|
726
|
+
parseEmojis(tabBar);
|
|
727
|
+
picker.appendChild(tabBar);
|
|
728
|
+
|
|
729
|
+
var scrollArea = document.createElement("div");
|
|
730
|
+
scrollArea.className = "emoji-picker-scroll";
|
|
731
|
+
|
|
732
|
+
var grid = document.createElement("div");
|
|
733
|
+
grid.className = "emoji-picker-grid";
|
|
734
|
+
scrollArea.appendChild(grid);
|
|
735
|
+
picker.appendChild(scrollArea);
|
|
736
|
+
|
|
737
|
+
function buildGrid(emojis) {
|
|
738
|
+
grid.innerHTML = "";
|
|
739
|
+
for (var i = 0; i < emojis.length; i++) {
|
|
740
|
+
(function (emoji) {
|
|
741
|
+
var btn = document.createElement("button");
|
|
742
|
+
btn.className = "emoji-picker-item";
|
|
743
|
+
btn.textContent = emoji;
|
|
744
|
+
btn.addEventListener("click", function (e) {
|
|
745
|
+
e.stopPropagation();
|
|
746
|
+
closeEmojiPicker();
|
|
747
|
+
if (_ctx.ws && _ctx.connected) {
|
|
748
|
+
_ctx.ws.send(JSON.stringify({ type: "set_project_icon", slug: slug, icon: emoji }));
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
grid.appendChild(btn);
|
|
752
|
+
})(emojis[i]);
|
|
753
|
+
}
|
|
754
|
+
parseEmojis(grid);
|
|
755
|
+
scrollArea.scrollTop = 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function switchCategory(idx) {
|
|
759
|
+
for (var j = 0; j < tabBtns.length; j++) {
|
|
760
|
+
tabBtns[j].classList.toggle("active", j === idx);
|
|
761
|
+
}
|
|
762
|
+
buildGrid(EMOJI_CATEGORIES[idx].emojis);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
buildGrid(EMOJI_CATEGORIES[0].emojis);
|
|
766
|
+
|
|
767
|
+
document.body.appendChild(picker);
|
|
768
|
+
emojiPickerEl = picker;
|
|
769
|
+
|
|
770
|
+
requestAnimationFrame(function () {
|
|
771
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
772
|
+
picker.style.left = (rect.right + 6) + "px";
|
|
773
|
+
picker.style.top = rect.top + "px";
|
|
774
|
+
var pRect = picker.getBoundingClientRect();
|
|
775
|
+
if (pRect.right > window.innerWidth - 8) {
|
|
776
|
+
picker.style.left = (rect.left - pRect.width - 6) + "px";
|
|
777
|
+
}
|
|
778
|
+
if (pRect.bottom > window.innerHeight - 8) {
|
|
779
|
+
picker.style.top = (window.innerHeight - pRect.height - 8) + "px";
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// --- Rename prompt ---
|
|
785
|
+
function showProjectRename(slug, currentName) {
|
|
786
|
+
var nameEl = document.getElementById("title-bar-project-name");
|
|
787
|
+
if (!nameEl) return;
|
|
788
|
+
|
|
789
|
+
var input = document.createElement("input");
|
|
790
|
+
input.type = "text";
|
|
791
|
+
input.className = "project-rename-input";
|
|
792
|
+
input.value = currentName || "";
|
|
793
|
+
|
|
794
|
+
var originalText = nameEl.textContent;
|
|
795
|
+
nameEl.textContent = "";
|
|
796
|
+
nameEl.appendChild(input);
|
|
797
|
+
input.focus();
|
|
798
|
+
input.select();
|
|
799
|
+
|
|
800
|
+
var committed = false;
|
|
801
|
+
|
|
802
|
+
function commitRename() {
|
|
803
|
+
if (committed) return;
|
|
804
|
+
committed = true;
|
|
805
|
+
var newName = input.value.trim();
|
|
806
|
+
if (newName && newName !== currentName && _ctx.ws && _ctx.connected) {
|
|
807
|
+
_ctx.ws.send(JSON.stringify({ type: "set_project_title", slug: slug, title: newName }));
|
|
808
|
+
nameEl.textContent = newName;
|
|
809
|
+
} else {
|
|
810
|
+
nameEl.textContent = originalText;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
input.addEventListener("keydown", function (e) {
|
|
815
|
+
e.stopPropagation();
|
|
816
|
+
if (e.key === "Enter") { e.preventDefault(); commitRename(); }
|
|
817
|
+
if (e.key === "Escape") { e.preventDefault(); committed = true; nameEl.textContent = originalText; }
|
|
818
|
+
});
|
|
819
|
+
input.addEventListener("blur", commitRename);
|
|
820
|
+
input.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// --- Drag-and-drop ---
|
|
824
|
+
|
|
825
|
+
function showTrashZone() {
|
|
826
|
+
var addBtn = document.getElementById("icon-strip-add");
|
|
827
|
+
if (!addBtn) return;
|
|
828
|
+
addBtn.style.display = "none";
|
|
829
|
+
|
|
830
|
+
var existing = document.getElementById("icon-strip-trash");
|
|
831
|
+
if (existing) existing.remove();
|
|
832
|
+
|
|
833
|
+
var trash = document.createElement("div");
|
|
834
|
+
trash.id = "icon-strip-trash";
|
|
835
|
+
trash.className = "icon-strip-trash";
|
|
836
|
+
trash.innerHTML = iconHtml("trash-2");
|
|
837
|
+
addBtn.parentNode.insertBefore(trash, addBtn.nextSibling);
|
|
838
|
+
refreshIcons();
|
|
839
|
+
|
|
840
|
+
trash.addEventListener("mouseenter", function () { _ctx.showIconTooltip(trash, "Remove project"); });
|
|
841
|
+
trash.addEventListener("mouseleave", _ctx.hideIconTooltip);
|
|
842
|
+
|
|
843
|
+
trash.addEventListener("dragover", function (e) {
|
|
844
|
+
e.preventDefault();
|
|
845
|
+
e.dataTransfer.dropEffect = "move";
|
|
846
|
+
trash.classList.add("drag-hover");
|
|
847
|
+
});
|
|
848
|
+
trash.addEventListener("dragleave", function () {
|
|
849
|
+
trash.classList.remove("drag-hover");
|
|
850
|
+
});
|
|
851
|
+
trash.addEventListener("drop", function (e) {
|
|
852
|
+
e.preventDefault();
|
|
853
|
+
trash.classList.remove("drag-hover");
|
|
854
|
+
var slug = e.dataTransfer.getData("text/plain");
|
|
855
|
+
if (slug && _ctx.ws && _ctx.connected) {
|
|
856
|
+
_ctx.ws.send(JSON.stringify({ type: "remove_project_check", slug: slug }));
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function hideTrashZone() {
|
|
862
|
+
var trash = document.getElementById("icon-strip-trash");
|
|
863
|
+
if (trash) trash.remove();
|
|
864
|
+
var addBtn = document.getElementById("icon-strip-add");
|
|
865
|
+
if (addBtn) addBtn.style.display = "";
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function clearDragIndicators() {
|
|
869
|
+
var items = document.querySelectorAll(".icon-strip-item.drag-over-above, .icon-strip-item.drag-over-below");
|
|
870
|
+
for (var i = 0; i < items.length; i++) {
|
|
871
|
+
items[i].classList.remove("drag-over-above", "drag-over-below");
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function setupDragHandlers(el, slug) {
|
|
876
|
+
el.setAttribute("draggable", "true");
|
|
877
|
+
|
|
878
|
+
el.addEventListener("dragstart", function (e) {
|
|
879
|
+
draggedSlug = slug;
|
|
880
|
+
draggedEl = el;
|
|
881
|
+
e.dataTransfer.effectAllowed = "move";
|
|
882
|
+
e.dataTransfer.setData("text/plain", slug);
|
|
883
|
+
|
|
884
|
+
var ghost = document.createElement("div");
|
|
885
|
+
ghost.textContent = el.textContent.trim().split("\n")[0];
|
|
886
|
+
ghost.style.cssText = "position:fixed;left:-200px;top:-200px;width:38px;height:38px;border-radius:12px;" +
|
|
887
|
+
"background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;" +
|
|
888
|
+
"font-size:15px;font-weight:600;pointer-events:none;z-index:-1;";
|
|
889
|
+
document.body.appendChild(ghost);
|
|
890
|
+
e.dataTransfer.setDragImage(ghost, 19, 19);
|
|
891
|
+
setTimeout(function () { ghost.remove(); }, 0);
|
|
892
|
+
|
|
893
|
+
setTimeout(function () { el.classList.add("dragging"); }, 0);
|
|
894
|
+
_ctx.hideIconTooltip();
|
|
895
|
+
showTrashZone();
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
el.addEventListener("dragover", function (e) {
|
|
899
|
+
e.preventDefault();
|
|
900
|
+
if (!draggedSlug || draggedSlug === slug) return;
|
|
901
|
+
e.dataTransfer.dropEffect = "move";
|
|
902
|
+
|
|
903
|
+
clearDragIndicators();
|
|
904
|
+
var rect = el.getBoundingClientRect();
|
|
905
|
+
var midY = rect.top + rect.height / 2;
|
|
906
|
+
if (e.clientY < midY) {
|
|
907
|
+
el.classList.add("drag-over-above");
|
|
908
|
+
} else {
|
|
909
|
+
el.classList.add("drag-over-below");
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
el.addEventListener("dragleave", function () {
|
|
914
|
+
el.classList.remove("drag-over-above", "drag-over-below");
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
el.addEventListener("drop", function (e) {
|
|
918
|
+
e.preventDefault();
|
|
919
|
+
clearDragIndicators();
|
|
920
|
+
if (!draggedSlug || draggedSlug === slug) return;
|
|
921
|
+
|
|
922
|
+
var rect = el.getBoundingClientRect();
|
|
923
|
+
var midY = rect.top + rect.height / 2;
|
|
924
|
+
var insertBefore = e.clientY < midY;
|
|
925
|
+
|
|
926
|
+
var container = document.getElementById("icon-strip-projects");
|
|
927
|
+
var items = container.querySelectorAll(".icon-strip-item");
|
|
928
|
+
var slugs = [];
|
|
929
|
+
for (var i = 0; i < items.length; i++) {
|
|
930
|
+
if (items[i].dataset.slug !== draggedSlug) {
|
|
931
|
+
slugs.push(items[i].dataset.slug);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
var targetIdx = slugs.indexOf(slug);
|
|
935
|
+
if (!insertBefore) targetIdx++;
|
|
936
|
+
slugs.splice(targetIdx, 0, draggedSlug);
|
|
937
|
+
|
|
938
|
+
if (_ctx.ws && _ctx.connected) {
|
|
939
|
+
_ctx.ws.send(JSON.stringify({ type: "reorder_projects", slugs: slugs }));
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
el.addEventListener("dragend", function () {
|
|
944
|
+
el.classList.remove("dragging");
|
|
945
|
+
clearDragIndicators();
|
|
946
|
+
draggedSlug = null;
|
|
947
|
+
draggedEl = null;
|
|
948
|
+
hideTrashZone();
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// --- Worktree folder collapse ---
|
|
953
|
+
|
|
954
|
+
function setWtCollapsed(slug, collapsed) {
|
|
955
|
+
wtCollapsed[slug] = collapsed;
|
|
956
|
+
try { localStorage.setItem("clay-wt-collapsed", JSON.stringify(wtCollapsed)); } catch (e) {}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function groupProjects(projects) {
|
|
960
|
+
var parents = [];
|
|
961
|
+
var wtByParent = {};
|
|
962
|
+
for (var i = 0; i < projects.length; i++) {
|
|
963
|
+
var p = projects[i];
|
|
964
|
+
if (p.isWorktree && p.parentSlug) {
|
|
965
|
+
if (!wtByParent[p.parentSlug]) wtByParent[p.parentSlug] = [];
|
|
966
|
+
wtByParent[p.parentSlug].push(p);
|
|
967
|
+
} else {
|
|
968
|
+
parents.push(p);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return { parents: parents, wtByParent: wtByParent };
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// --- Icon item creation ---
|
|
975
|
+
|
|
976
|
+
function createIconItem(p, currentSlug) {
|
|
977
|
+
var currentDmUserId = _ctx.getCurrentDmUserId ? _ctx.getCurrentDmUserId() : null;
|
|
978
|
+
var el = document.createElement("a");
|
|
979
|
+
var isActive = p.slug === currentSlug && !currentDmUserId;
|
|
980
|
+
el.className = "icon-strip-item" + (isActive ? " active" : "");
|
|
981
|
+
el.href = "/p/" + p.slug + "/";
|
|
982
|
+
el.dataset.slug = p.slug;
|
|
983
|
+
|
|
984
|
+
if (p.icon) {
|
|
985
|
+
var emojiSpan = document.createElement("span");
|
|
986
|
+
emojiSpan.className = "project-emoji";
|
|
987
|
+
emojiSpan.textContent = p.icon;
|
|
988
|
+
parseEmojis(emojiSpan);
|
|
989
|
+
el.appendChild(emojiSpan);
|
|
990
|
+
} else {
|
|
991
|
+
el.appendChild(document.createTextNode(getProjectAbbrev(p.name)));
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
var pill = document.createElement("span");
|
|
995
|
+
pill.className = "icon-strip-pill";
|
|
996
|
+
el.appendChild(pill);
|
|
997
|
+
|
|
998
|
+
var statusDot = document.createElement("span");
|
|
999
|
+
statusDot.className = "icon-strip-status";
|
|
1000
|
+
if (p.isProcessing) statusDot.classList.add("processing");
|
|
1001
|
+
el.appendChild(statusDot);
|
|
1002
|
+
|
|
1003
|
+
var projectBadge = document.createElement("span");
|
|
1004
|
+
projectBadge.className = "icon-strip-project-badge";
|
|
1005
|
+
if (p.unread > 0 && !isActive) {
|
|
1006
|
+
projectBadge.textContent = p.unread > 99 ? "99+" : String(p.unread);
|
|
1007
|
+
projectBadge.classList.add("has-unread");
|
|
1008
|
+
}
|
|
1009
|
+
el.appendChild(projectBadge);
|
|
1010
|
+
|
|
1011
|
+
if (p.pendingPermissions > 0 && !isActive) {
|
|
1012
|
+
el.classList.add("has-pending-perm");
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
(function (name, elem) {
|
|
1016
|
+
elem.addEventListener("mouseenter", function () { _ctx.showIconTooltip(elem, name); });
|
|
1017
|
+
elem.addEventListener("mouseleave", _ctx.hideIconTooltip);
|
|
1018
|
+
})(p.name, el);
|
|
1019
|
+
|
|
1020
|
+
(function (slug) {
|
|
1021
|
+
el.addEventListener("click", function (e) {
|
|
1022
|
+
e.preventDefault();
|
|
1023
|
+
if (_ctx.switchProject) _ctx.switchProject(slug);
|
|
1024
|
+
});
|
|
1025
|
+
})(p.slug);
|
|
1026
|
+
|
|
1027
|
+
return el;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// --- Worktree creation modal ---
|
|
1031
|
+
|
|
1032
|
+
function showWorktreeModal(parentSlug, parentName) {
|
|
1033
|
+
var existing = document.getElementById("wt-modal-container");
|
|
1034
|
+
if (existing) existing.remove();
|
|
1035
|
+
|
|
1036
|
+
var container = document.createElement("div");
|
|
1037
|
+
container.id = "wt-modal-container";
|
|
1038
|
+
|
|
1039
|
+
var overlay = document.createElement("div");
|
|
1040
|
+
overlay.className = "wt-modal-overlay";
|
|
1041
|
+
container.appendChild(overlay);
|
|
1042
|
+
|
|
1043
|
+
var modal = document.createElement("div");
|
|
1044
|
+
modal.className = "wt-modal";
|
|
1045
|
+
|
|
1046
|
+
var title = document.createElement("div");
|
|
1047
|
+
title.className = "wt-modal-title";
|
|
1048
|
+
title.textContent = "Add Worktree \u2014 " + parentName;
|
|
1049
|
+
modal.appendChild(title);
|
|
1050
|
+
|
|
1051
|
+
var branchLabel = document.createElement("label");
|
|
1052
|
+
branchLabel.className = "wt-modal-label";
|
|
1053
|
+
branchLabel.textContent = "Branch name";
|
|
1054
|
+
modal.appendChild(branchLabel);
|
|
1055
|
+
|
|
1056
|
+
var branchInput = document.createElement("input");
|
|
1057
|
+
branchInput.type = "text";
|
|
1058
|
+
branchInput.className = "wt-modal-input";
|
|
1059
|
+
branchInput.placeholder = "feat/my-feature";
|
|
1060
|
+
branchInput.autocomplete = "off";
|
|
1061
|
+
branchInput.spellcheck = false;
|
|
1062
|
+
modal.appendChild(branchInput);
|
|
1063
|
+
|
|
1064
|
+
var baseLabel = document.createElement("label");
|
|
1065
|
+
baseLabel.className = "wt-modal-label";
|
|
1066
|
+
baseLabel.textContent = "Base branch";
|
|
1067
|
+
modal.appendChild(baseLabel);
|
|
1068
|
+
|
|
1069
|
+
var baseSelect = document.createElement("select");
|
|
1070
|
+
baseSelect.className = "wt-modal-input";
|
|
1071
|
+
var defaultOpt = document.createElement("option");
|
|
1072
|
+
defaultOpt.value = "main";
|
|
1073
|
+
defaultOpt.textContent = "main";
|
|
1074
|
+
baseSelect.appendChild(defaultOpt);
|
|
1075
|
+
modal.appendChild(baseSelect);
|
|
1076
|
+
|
|
1077
|
+
fetch("/p/" + parentSlug + "/api/branches")
|
|
1078
|
+
.then(function (res) { return res.json(); })
|
|
1079
|
+
.then(function (data) {
|
|
1080
|
+
baseSelect.innerHTML = "";
|
|
1081
|
+
var branches = data.branches || ["main"];
|
|
1082
|
+
var defBranch = data.defaultBranch || "main";
|
|
1083
|
+
for (var i = 0; i < branches.length; i++) {
|
|
1084
|
+
var opt = document.createElement("option");
|
|
1085
|
+
opt.value = branches[i];
|
|
1086
|
+
opt.textContent = branches[i];
|
|
1087
|
+
if (branches[i] === defBranch) opt.selected = true;
|
|
1088
|
+
baseSelect.appendChild(opt);
|
|
1089
|
+
}
|
|
1090
|
+
})
|
|
1091
|
+
.catch(function () {});
|
|
1092
|
+
|
|
1093
|
+
var errorDiv = document.createElement("div");
|
|
1094
|
+
errorDiv.className = "wt-modal-error";
|
|
1095
|
+
modal.appendChild(errorDiv);
|
|
1096
|
+
|
|
1097
|
+
var actions = document.createElement("div");
|
|
1098
|
+
actions.className = "wt-modal-actions";
|
|
1099
|
+
|
|
1100
|
+
var cancelBtn = document.createElement("button");
|
|
1101
|
+
cancelBtn.className = "wt-modal-btn";
|
|
1102
|
+
cancelBtn.textContent = "Cancel";
|
|
1103
|
+
actions.appendChild(cancelBtn);
|
|
1104
|
+
|
|
1105
|
+
var createBtn = document.createElement("button");
|
|
1106
|
+
createBtn.className = "wt-modal-btn primary";
|
|
1107
|
+
createBtn.textContent = "Create";
|
|
1108
|
+
actions.appendChild(createBtn);
|
|
1109
|
+
|
|
1110
|
+
modal.appendChild(actions);
|
|
1111
|
+
container.appendChild(modal);
|
|
1112
|
+
document.body.appendChild(container);
|
|
1113
|
+
branchInput.focus();
|
|
1114
|
+
|
|
1115
|
+
function closeModal() { container.remove(); }
|
|
1116
|
+
|
|
1117
|
+
function doCreate() {
|
|
1118
|
+
var branch = branchInput.value.trim();
|
|
1119
|
+
var base = baseSelect.value.trim() || null;
|
|
1120
|
+
if (!branch) {
|
|
1121
|
+
errorDiv.textContent = "Branch name is required";
|
|
1122
|
+
errorDiv.classList.add("visible");
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
var dirName = branch.replace(/\//g, "-");
|
|
1126
|
+
createBtn.disabled = true;
|
|
1127
|
+
createBtn.textContent = "Creating...";
|
|
1128
|
+
errorDiv.classList.remove("visible");
|
|
1129
|
+
|
|
1130
|
+
if (_ctx.ws && _ctx.connected) {
|
|
1131
|
+
_ctx.ws.send(JSON.stringify({
|
|
1132
|
+
type: "create_worktree",
|
|
1133
|
+
branch: branch,
|
|
1134
|
+
dirName: dirName,
|
|
1135
|
+
baseBranch: base
|
|
1136
|
+
}));
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
var handler = function (event) {
|
|
1140
|
+
var msg;
|
|
1141
|
+
try { msg = JSON.parse(event.data); } catch (e) { return; }
|
|
1142
|
+
if (msg.type === "create_worktree_result") {
|
|
1143
|
+
_ctx.ws.removeEventListener("message", handler);
|
|
1144
|
+
if (msg.ok) {
|
|
1145
|
+
closeModal();
|
|
1146
|
+
if (msg.slug && _ctx.switchProject) _ctx.switchProject(msg.slug);
|
|
1147
|
+
} else {
|
|
1148
|
+
createBtn.disabled = false;
|
|
1149
|
+
createBtn.textContent = "Create";
|
|
1150
|
+
errorDiv.textContent = msg.error || "Failed to create worktree";
|
|
1151
|
+
errorDiv.classList.add("visible");
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
_ctx.ws.addEventListener("message", handler);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
overlay.addEventListener("click", closeModal);
|
|
1159
|
+
cancelBtn.addEventListener("click", closeModal);
|
|
1160
|
+
createBtn.addEventListener("click", doCreate);
|
|
1161
|
+
branchInput.addEventListener("keydown", function (e) {
|
|
1162
|
+
if (e.key === "Enter") doCreate();
|
|
1163
|
+
if (e.key === "Escape") closeModal();
|
|
1164
|
+
});
|
|
1165
|
+
baseSelect.addEventListener("keydown", function (e) {
|
|
1166
|
+
if (e.key === "Enter") doCreate();
|
|
1167
|
+
if (e.key === "Escape") closeModal();
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// --- Render icon strip ---
|
|
1172
|
+
|
|
1173
|
+
export function renderIconStrip(projects, currentSlug) {
|
|
1174
|
+
cachedProjectList = projects;
|
|
1175
|
+
cachedCurrentSlug = currentSlug;
|
|
1176
|
+
|
|
1177
|
+
var container = document.getElementById("icon-strip-projects");
|
|
1178
|
+
if (!container) return;
|
|
1179
|
+
container.innerHTML = "";
|
|
1180
|
+
|
|
1181
|
+
var currentDmUserId = _ctx.getCurrentDmUserId ? _ctx.getCurrentDmUserId() : null;
|
|
1182
|
+
var grouped = groupProjects(projects);
|
|
1183
|
+
|
|
1184
|
+
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1185
|
+
var p = grouped.parents[i];
|
|
1186
|
+
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1187
|
+
var hasWorktrees = worktrees.length > 0;
|
|
1188
|
+
|
|
1189
|
+
if (!hasWorktrees) {
|
|
1190
|
+
var el = createIconItem(p, currentSlug);
|
|
1191
|
+
(function (slug, name, elem) {
|
|
1192
|
+
elem.addEventListener("contextmenu", function (e) {
|
|
1193
|
+
e.preventDefault();
|
|
1194
|
+
e.stopPropagation();
|
|
1195
|
+
showIconCtxMenu(elem, slug, name);
|
|
1196
|
+
});
|
|
1197
|
+
})(p.slug, p.name || p.slug, el);
|
|
1198
|
+
setupDragHandlers(el, p.slug);
|
|
1199
|
+
container.appendChild(el);
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Folder group for parent + worktrees
|
|
1204
|
+
var folder = document.createElement("div");
|
|
1205
|
+
folder.className = "icon-strip-group";
|
|
1206
|
+
folder.dataset.parentSlug = p.slug;
|
|
1207
|
+
if (wtCollapsed[p.slug]) folder.classList.add("collapsed");
|
|
1208
|
+
|
|
1209
|
+
if (!p.isProcessing) {
|
|
1210
|
+
for (var wpi = 0; wpi < worktrees.length; wpi++) {
|
|
1211
|
+
if (worktrees[wpi].isProcessing) { p.isProcessing = true; break; }
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
var header = createIconItem(p, currentSlug);
|
|
1216
|
+
header.classList.add("folder-header");
|
|
1217
|
+
(function (slug, name, elem) {
|
|
1218
|
+
elem.addEventListener("contextmenu", function (e) {
|
|
1219
|
+
e.preventDefault();
|
|
1220
|
+
e.stopPropagation();
|
|
1221
|
+
showIconCtxMenu(elem, slug, name);
|
|
1222
|
+
});
|
|
1223
|
+
})(p.slug, p.name || p.slug, header);
|
|
1224
|
+
setupDragHandlers(header, p.slug);
|
|
1225
|
+
|
|
1226
|
+
var chevron = document.createElement("span");
|
|
1227
|
+
chevron.className = "icon-strip-group-chevron";
|
|
1228
|
+
chevron.innerHTML = '<i data-lucide="git-branch"></i>';
|
|
1229
|
+
(function (parentSlug, folderEl) {
|
|
1230
|
+
chevron.addEventListener("click", function (e) {
|
|
1231
|
+
e.preventDefault();
|
|
1232
|
+
e.stopPropagation();
|
|
1233
|
+
var nowCollapsed = folderEl.classList.toggle("collapsed");
|
|
1234
|
+
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1235
|
+
});
|
|
1236
|
+
chevron.addEventListener("contextmenu", function (e) {
|
|
1237
|
+
e.preventDefault();
|
|
1238
|
+
e.stopPropagation();
|
|
1239
|
+
});
|
|
1240
|
+
})(p.slug, folder);
|
|
1241
|
+
chevron.setAttribute("data-tip", "Toggle worktrees");
|
|
1242
|
+
header.appendChild(chevron);
|
|
1243
|
+
folder.appendChild(header);
|
|
1244
|
+
|
|
1245
|
+
var itemsContainer = document.createElement("div");
|
|
1246
|
+
itemsContainer.className = "icon-strip-group-items";
|
|
1247
|
+
|
|
1248
|
+
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1249
|
+
(function (wt) {
|
|
1250
|
+
var wtEl = document.createElement("a");
|
|
1251
|
+
var isWtActive = wt.slug === currentSlug && !currentDmUserId;
|
|
1252
|
+
var isAccessible = wt.worktreeAccessible !== false;
|
|
1253
|
+
wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (!isAccessible ? " wt-disabled" : "");
|
|
1254
|
+
wtEl.href = "/p/" + wt.slug + "/";
|
|
1255
|
+
wtEl.dataset.slug = wt.slug;
|
|
1256
|
+
|
|
1257
|
+
var abbrev = document.createElement("span");
|
|
1258
|
+
abbrev.className = "wt-branch-abbrev";
|
|
1259
|
+
abbrev.textContent = getProjectAbbrev(wt.name);
|
|
1260
|
+
wtEl.appendChild(abbrev);
|
|
1261
|
+
|
|
1262
|
+
var wtStatus = document.createElement("span");
|
|
1263
|
+
wtStatus.className = "icon-strip-status";
|
|
1264
|
+
if (wt.isProcessing) wtStatus.classList.add("processing");
|
|
1265
|
+
wtEl.appendChild(wtStatus);
|
|
1266
|
+
|
|
1267
|
+
var tooltipText = wt.name;
|
|
1268
|
+
if (!isAccessible) {
|
|
1269
|
+
tooltipText += " (outside project path, cannot be accessed)";
|
|
1270
|
+
}
|
|
1271
|
+
(function (text, elem) {
|
|
1272
|
+
elem.addEventListener("mouseenter", function () { _ctx.showIconTooltip(elem, text); });
|
|
1273
|
+
elem.addEventListener("mouseleave", _ctx.hideIconTooltip);
|
|
1274
|
+
})(tooltipText, wtEl);
|
|
1275
|
+
|
|
1276
|
+
if (isAccessible) {
|
|
1277
|
+
(function (slug) {
|
|
1278
|
+
wtEl.addEventListener("click", function (e) {
|
|
1279
|
+
e.preventDefault();
|
|
1280
|
+
if (_ctx.switchProject) _ctx.switchProject(slug);
|
|
1281
|
+
});
|
|
1282
|
+
})(wt.slug);
|
|
1283
|
+
} else {
|
|
1284
|
+
wtEl.addEventListener("click", function (e) {
|
|
1285
|
+
e.preventDefault();
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
if (isAccessible) {
|
|
1290
|
+
(function (slug, name, elem) {
|
|
1291
|
+
elem.addEventListener("contextmenu", function (e) {
|
|
1292
|
+
e.preventDefault();
|
|
1293
|
+
e.stopPropagation();
|
|
1294
|
+
showIconCtxMenu(elem, slug, name);
|
|
1295
|
+
});
|
|
1296
|
+
})(wt.slug, wt.name, wtEl);
|
|
1297
|
+
} else {
|
|
1298
|
+
wtEl.addEventListener("contextmenu", function (e) {
|
|
1299
|
+
e.preventDefault();
|
|
1300
|
+
e.stopPropagation();
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (wt.pendingPermissions > 0 && !isWtActive) {
|
|
1305
|
+
wtEl.classList.add("has-pending-perm");
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
itemsContainer.appendChild(wtEl);
|
|
1309
|
+
})(worktrees[wi]);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
var hasWtPendingPerm = false;
|
|
1313
|
+
for (var wpi2 = 0; wpi2 < worktrees.length; wpi2++) {
|
|
1314
|
+
if (worktrees[wpi2].pendingPermissions > 0) { hasWtPendingPerm = true; break; }
|
|
1315
|
+
}
|
|
1316
|
+
if (hasWtPendingPerm) folder.classList.remove("collapsed");
|
|
1317
|
+
|
|
1318
|
+
var addBtn = document.createElement("button");
|
|
1319
|
+
addBtn.className = "icon-strip-group-add";
|
|
1320
|
+
addBtn.textContent = "+";
|
|
1321
|
+
(function (parentSlug, parentName, btn) {
|
|
1322
|
+
btn.addEventListener("click", function (e) {
|
|
1323
|
+
e.preventDefault();
|
|
1324
|
+
e.stopPropagation();
|
|
1325
|
+
showWorktreeModal(parentSlug, parentName);
|
|
1326
|
+
});
|
|
1327
|
+
btn.addEventListener("mouseenter", function () { _ctx.showIconTooltip(btn, "New worktree"); });
|
|
1328
|
+
btn.addEventListener("mouseleave", _ctx.hideIconTooltip);
|
|
1329
|
+
})(p.slug, p.name, addBtn);
|
|
1330
|
+
itemsContainer.appendChild(addBtn);
|
|
1331
|
+
|
|
1332
|
+
folder.appendChild(itemsContainer);
|
|
1333
|
+
container.appendChild(folder);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// Update home icon active state
|
|
1337
|
+
var homeIcon = document.querySelector(".icon-strip-home");
|
|
1338
|
+
if (homeIcon) {
|
|
1339
|
+
if ((!currentSlug || projects.length === 0) && !currentDmUserId) {
|
|
1340
|
+
homeIcon.classList.add("active");
|
|
1341
|
+
} else {
|
|
1342
|
+
homeIcon.classList.remove("active");
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
renderProjectList(projects, currentSlug);
|
|
1347
|
+
|
|
1348
|
+
try { lucide.createIcons({ nodes: [container] }); } catch (e) {}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function renderProjectList(projects, currentSlug) {
|
|
1352
|
+
var list = document.getElementById("project-list");
|
|
1353
|
+
if (!list) return;
|
|
1354
|
+
list.innerHTML = "";
|
|
1355
|
+
|
|
1356
|
+
var grouped = groupProjects(projects);
|
|
1357
|
+
|
|
1358
|
+
for (var i = 0; i < grouped.parents.length; i++) {
|
|
1359
|
+
var p = grouped.parents[i];
|
|
1360
|
+
var worktrees = grouped.wtByParent[p.slug] || [];
|
|
1361
|
+
|
|
1362
|
+
if (worktrees.length === 0) {
|
|
1363
|
+
list.appendChild(createMobileProjectItem(p, currentSlug, false));
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
var folderDiv = document.createElement("div");
|
|
1368
|
+
folderDiv.className = "mobile-project-folder";
|
|
1369
|
+
if (wtCollapsed[p.slug]) folderDiv.classList.add("collapsed");
|
|
1370
|
+
|
|
1371
|
+
var headerEl = createMobileProjectItem(p, currentSlug, false);
|
|
1372
|
+
var chevron = document.createElement("span");
|
|
1373
|
+
chevron.className = "mobile-folder-chevron";
|
|
1374
|
+
chevron.innerHTML = "▼";
|
|
1375
|
+
(function (parentSlug, fDiv) {
|
|
1376
|
+
chevron.addEventListener("click", function (e) {
|
|
1377
|
+
e.preventDefault();
|
|
1378
|
+
e.stopPropagation();
|
|
1379
|
+
var nowCollapsed = fDiv.classList.toggle("collapsed");
|
|
1380
|
+
setWtCollapsed(parentSlug, nowCollapsed);
|
|
1381
|
+
});
|
|
1382
|
+
})(p.slug, folderDiv);
|
|
1383
|
+
headerEl.appendChild(chevron);
|
|
1384
|
+
folderDiv.appendChild(headerEl);
|
|
1385
|
+
|
|
1386
|
+
var wtList = document.createElement("div");
|
|
1387
|
+
wtList.className = "mobile-folder-items";
|
|
1388
|
+
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
1389
|
+
var isAccessible = worktrees[wi].worktreeAccessible !== false;
|
|
1390
|
+
var wtItem = createMobileProjectItem(worktrees[wi], currentSlug, true);
|
|
1391
|
+
if (!isAccessible) wtItem.classList.add("wt-disabled");
|
|
1392
|
+
if (!isAccessible) {
|
|
1393
|
+
wtItem.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); });
|
|
1394
|
+
}
|
|
1395
|
+
wtList.appendChild(wtItem);
|
|
1396
|
+
}
|
|
1397
|
+
folderDiv.appendChild(wtList);
|
|
1398
|
+
list.appendChild(folderDiv);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function createMobileProjectItem(p, currentSlug, isWorktree) {
|
|
1403
|
+
var el = document.createElement("button");
|
|
1404
|
+
el.className = "mobile-project-item" + (p.slug === currentSlug ? " active" : "") + (isWorktree ? " wt-item" : "");
|
|
1405
|
+
|
|
1406
|
+
var abbrev = document.createElement("span");
|
|
1407
|
+
abbrev.className = "mobile-project-abbrev";
|
|
1408
|
+
if (p.icon) {
|
|
1409
|
+
abbrev.textContent = p.icon;
|
|
1410
|
+
parseEmojis(abbrev);
|
|
1411
|
+
} else {
|
|
1412
|
+
abbrev.textContent = getProjectAbbrev(p.name);
|
|
1413
|
+
}
|
|
1414
|
+
el.appendChild(abbrev);
|
|
1415
|
+
|
|
1416
|
+
var name = document.createElement("span");
|
|
1417
|
+
name.className = "mobile-project-name";
|
|
1418
|
+
name.textContent = p.name;
|
|
1419
|
+
el.appendChild(name);
|
|
1420
|
+
|
|
1421
|
+
if (p.isProcessing) {
|
|
1422
|
+
var dot = document.createElement("span");
|
|
1423
|
+
dot.className = "mobile-project-processing";
|
|
1424
|
+
el.appendChild(dot);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
el.addEventListener("click", function () {
|
|
1428
|
+
if (_ctx.switchProject) _ctx.switchProject(p.slug);
|
|
1429
|
+
if (_ctx.closeSidebar) _ctx.closeSidebar();
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
return el;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
export function getEmojiCategories() { return EMOJI_CATEGORIES; }
|
|
1436
|
+
|
|
1437
|
+
export function updateProjectBadge(slug, count) {
|
|
1438
|
+
var icon = document.querySelector('.icon-strip-item[data-slug="' + slug + '"]');
|
|
1439
|
+
if (!icon) return;
|
|
1440
|
+
var badge = icon.querySelector(".icon-strip-project-badge");
|
|
1441
|
+
if (!badge) return;
|
|
1442
|
+
if (count > 0) {
|
|
1443
|
+
badge.textContent = count > 99 ? "99+" : String(count);
|
|
1444
|
+
badge.classList.add("has-unread");
|
|
1445
|
+
} else {
|
|
1446
|
+
badge.textContent = "";
|
|
1447
|
+
badge.classList.remove("has-unread");
|
|
1448
|
+
}
|
|
1449
|
+
}
|