clay-server 2.27.0-beta.9 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +13 -126
- package/lib/mates-identity.js +132 -0
- package/lib/mates-knowledge.js +113 -0
- package/lib/mates-prompts.js +398 -0
- package/lib/mates.js +40 -599
- package/lib/project-connection.js +2 -0
- package/lib/project-http.js +4 -2
- package/lib/project-loop.js +110 -48
- package/lib/project-mate-interaction.js +4 -0
- package/lib/project-notifications.js +210 -0
- package/lib/project-sessions.js +5 -2
- package/lib/project-user-message.js +2 -1
- package/lib/project.js +26 -2
- package/lib/public/app.js +1193 -8517
- package/lib/public/css/command-palette.css +14 -0
- package/lib/public/css/loop.css +301 -0
- package/lib/public/css/notifications-center.css +190 -0
- package/lib/public/css/rewind.css +6 -0
- package/lib/public/index.html +89 -35
- 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 +389 -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 +589 -0
- package/lib/public/modules/app-loop-wizard.js +439 -0
- package/lib/public/modules/app-messages.js +1560 -0
- package/lib/public/modules/app-misc.js +299 -0
- package/lib/public/modules/app-notifications.js +372 -0
- package/lib/public/modules/app-panels.js +888 -0
- package/lib/public/modules/app-projects.js +798 -0
- package/lib/public/modules/app-rate-limit.js +451 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +234 -0
- package/lib/public/modules/command-palette.js +27 -4
- package/lib/public/modules/input.js +31 -20
- package/lib/public/modules/scheduler-config.js +1532 -0
- package/lib/public/modules/scheduler-history.js +79 -0
- package/lib/public/modules/scheduler.js +33 -1554
- package/lib/public/modules/session-search.js +13 -1
- package/lib/public/modules/sidebar-mates.js +812 -0
- package/lib/public/modules/sidebar-mobile.js +1269 -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/public/modules/store.js +27 -0
- package/lib/public/modules/ws-ref.js +7 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +96 -717
- package/lib/sdk-message-processor.js +587 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/server-admin.js +712 -0
- package/lib/server-auth.js +737 -0
- package/lib/server-dm.js +221 -0
- package/lib/server-mates.js +281 -0
- package/lib/server-palette.js +110 -0
- package/lib/server-settings.js +479 -0
- package/lib/server-skills.js +280 -0
- package/lib/server.js +246 -2755
- package/lib/sessions.js +11 -4
- package/lib/users-auth.js +146 -0
- package/lib/users-permissions.js +118 -0
- package/lib/users-preferences.js +210 -0
- package/lib/users.js +48 -398
- package/lib/ws-schema.js +498 -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
|
+
}
|