clay-server 2.19.0 → 2.20.0-beta.2
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 +51 -91
- package/bin/cli.js +49 -14
- package/lib/builtin-mates.js +360 -0
- package/lib/cli-sessions.js +3 -3
- package/lib/config.js +30 -4
- package/lib/daemon.js +28 -5
- package/lib/mates.js +169 -7
- package/lib/notes.js +20 -0
- package/lib/os-users.js +71 -2
- package/lib/project.js +903 -228
- package/lib/public/app.js +249 -69
- package/lib/public/css/icon-strip.css +55 -2
- package/lib/public/css/input.css +50 -30
- package/lib/public/css/mates.css +316 -2
- package/lib/public/css/mention.css +23 -0
- package/lib/public/css/mobile-nav.css +198 -0
- package/lib/public/css/overlays.css +23 -0
- package/lib/public/css/rewind.css +32 -0
- package/lib/public/css/title-bar.css +3 -0
- package/lib/public/css/user-settings.css +2 -2
- package/lib/public/index.html +65 -14
- package/lib/public/mates/ally.png +0 -0
- package/lib/public/mates/sage.jpg +0 -0
- package/lib/public/mates/scout.png +0 -0
- package/lib/public/modules/command-palette.js +44 -4
- package/lib/public/modules/filebrowser.js +2 -0
- package/lib/public/modules/input.js +158 -16
- package/lib/public/modules/mate-knowledge.js +2 -0
- package/lib/public/modules/mate-memory.js +353 -0
- package/lib/public/modules/mention.js +77 -2
- package/lib/public/modules/notifications.js +0 -8
- package/lib/public/modules/server-settings.js +11 -4
- package/lib/public/modules/sidebar.js +507 -21
- package/lib/public/modules/theme.js +10 -12
- package/lib/public/modules/tools.js +84 -12
- package/lib/public/modules/user-settings.js +45 -12
- package/lib/sdk-bridge.js +122 -61
- package/lib/server.js +209 -13
- package/lib/sessions.js +4 -4
- package/lib/users.js +89 -0
- package/package.json +1 -1
|
@@ -20,6 +20,8 @@ var searchDebounce = null;
|
|
|
20
20
|
var cachedSessions = [];
|
|
21
21
|
var expandedLoopGroups = new Set();
|
|
22
22
|
var expandedLoopRuns = new Set();
|
|
23
|
+
var expandedMobileLoopGroups = new Set();
|
|
24
|
+
var expandedMobileLoopRuns = new Set();
|
|
23
25
|
|
|
24
26
|
// --- Cached project data for mobile sheet ---
|
|
25
27
|
var cachedProjectList = [];
|
|
@@ -959,7 +961,19 @@ function renderSheetSessions(listEl) {
|
|
|
959
961
|
})(cachedProjectList[ci]);
|
|
960
962
|
}
|
|
961
963
|
|
|
962
|
-
|
|
964
|
+
var favoriteChipMates = cachedMates.filter(function (m) {
|
|
965
|
+
if (cachedDmRemovedUsers[m.id]) return false;
|
|
966
|
+
if (cachedDmFavorites.indexOf(m.id) !== -1) return true;
|
|
967
|
+
if (cachedDmUnread[m.id] && cachedDmUnread[m.id] > 0) return true;
|
|
968
|
+
return false;
|
|
969
|
+
});
|
|
970
|
+
var sortedChipMates = favoriteChipMates.sort(function (a, b) {
|
|
971
|
+
var aBuiltin = a.builtinKey ? 1 : 0;
|
|
972
|
+
var bBuiltin = b.builtinKey ? 1 : 0;
|
|
973
|
+
if (aBuiltin !== bBuiltin) return bBuiltin - aBuiltin;
|
|
974
|
+
return (a.createdAt || 0) - (b.createdAt || 0);
|
|
975
|
+
});
|
|
976
|
+
for (var mi = 0; mi < sortedChipMates.length; mi++) {
|
|
963
977
|
(function (mate) {
|
|
964
978
|
var mp = mate.profile || {};
|
|
965
979
|
var chip = document.createElement("button");
|
|
@@ -998,7 +1012,7 @@ function renderSheetSessions(listEl) {
|
|
|
998
1012
|
}
|
|
999
1013
|
|
|
1000
1014
|
chips.push(chip);
|
|
1001
|
-
})(
|
|
1015
|
+
})(sortedChipMates[mi]);
|
|
1002
1016
|
}
|
|
1003
1017
|
|
|
1004
1018
|
for (var i = 0; i < chips.length; i++) {
|
|
@@ -1104,7 +1118,242 @@ function createMobileSessionItem(s) {
|
|
|
1104
1118
|
return el;
|
|
1105
1119
|
}
|
|
1106
1120
|
|
|
1107
|
-
// Helper:
|
|
1121
|
+
// Helper: create a mobile loop child element (individual session inside a group)
|
|
1122
|
+
function createMobileLoopChild(s) {
|
|
1123
|
+
var el = document.createElement("button");
|
|
1124
|
+
el.className = "mobile-loop-child" + (s.active ? " active" : "");
|
|
1125
|
+
|
|
1126
|
+
if (s.isProcessing) {
|
|
1127
|
+
var dot = document.createElement("span");
|
|
1128
|
+
dot.className = "mobile-session-processing";
|
|
1129
|
+
el.appendChild(dot);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
var textSpan = document.createElement("span");
|
|
1133
|
+
textSpan.className = "mobile-session-title";
|
|
1134
|
+
if (s.loop) {
|
|
1135
|
+
var isRalphChild = s.loop.source === "ralph";
|
|
1136
|
+
var roleName = s.loop.role === "crafting" ? "Crafting" : s.loop.role === "judge" ? "Judge" : (isRalphChild ? "Coder" : "Run");
|
|
1137
|
+
var iterSuffix = s.loop.role === "crafting" ? "" : " #" + s.loop.iteration;
|
|
1138
|
+
var roleCls = s.loop.role === "crafting" ? " crafting" : (!isRalphChild ? " scheduled" : "");
|
|
1139
|
+
var badge = document.createElement("span");
|
|
1140
|
+
badge.className = "mobile-loop-role-badge" + roleCls;
|
|
1141
|
+
badge.textContent = roleName + iterSuffix;
|
|
1142
|
+
textSpan.appendChild(badge);
|
|
1143
|
+
}
|
|
1144
|
+
el.appendChild(textSpan);
|
|
1145
|
+
|
|
1146
|
+
(function (id) {
|
|
1147
|
+
el.addEventListener("click", function () {
|
|
1148
|
+
if (ctx.ws && ctx.connected) {
|
|
1149
|
+
ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
|
|
1150
|
+
}
|
|
1151
|
+
dismissOverlayPanels();
|
|
1152
|
+
closeMobileSheet();
|
|
1153
|
+
});
|
|
1154
|
+
})(s.id);
|
|
1155
|
+
|
|
1156
|
+
return el;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Helper: create a mobile loop run sub-group (collapsible time group)
|
|
1160
|
+
function createMobileLoopRun(parentGk, startedAtKey, sessions, isRalph) {
|
|
1161
|
+
var runGk = parentGk + ":" + startedAtKey;
|
|
1162
|
+
var expanded = expandedMobileLoopRuns.has(runGk);
|
|
1163
|
+
var startedAt = Number(startedAtKey);
|
|
1164
|
+
var timeLabel = startedAt ? new Date(startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "Unknown";
|
|
1165
|
+
|
|
1166
|
+
var hasActive = false;
|
|
1167
|
+
var anyProcessing = false;
|
|
1168
|
+
var latestSession = sessions[0];
|
|
1169
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
1170
|
+
if (sessions[i].active) hasActive = true;
|
|
1171
|
+
if (sessions[i].isProcessing) anyProcessing = true;
|
|
1172
|
+
if ((sessions[i].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
1173
|
+
latestSession = sessions[i];
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
var wrapper = document.createElement("div");
|
|
1178
|
+
wrapper.className = "mobile-loop-run-wrapper";
|
|
1179
|
+
|
|
1180
|
+
var header = document.createElement("button");
|
|
1181
|
+
header.className = "mobile-loop-run" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
|
|
1182
|
+
|
|
1183
|
+
var chevron = document.createElement("span");
|
|
1184
|
+
chevron.className = "mobile-loop-chevron";
|
|
1185
|
+
chevron.innerHTML = iconHtml("chevron-right");
|
|
1186
|
+
header.appendChild(chevron);
|
|
1187
|
+
|
|
1188
|
+
var label = document.createElement("span");
|
|
1189
|
+
label.className = "mobile-loop-run-time";
|
|
1190
|
+
var labelHtml = "";
|
|
1191
|
+
if (anyProcessing) {
|
|
1192
|
+
labelHtml += '<span class="mobile-session-processing"></span> ';
|
|
1193
|
+
}
|
|
1194
|
+
labelHtml += escapeHtml(timeLabel);
|
|
1195
|
+
label.innerHTML = labelHtml;
|
|
1196
|
+
header.appendChild(label);
|
|
1197
|
+
|
|
1198
|
+
var countBadge = document.createElement("span");
|
|
1199
|
+
countBadge.className = "mobile-loop-count" + (isRalph ? "" : " scheduled");
|
|
1200
|
+
countBadge.textContent = String(sessions.length);
|
|
1201
|
+
header.appendChild(countBadge);
|
|
1202
|
+
|
|
1203
|
+
header.addEventListener("click", (function (rk) {
|
|
1204
|
+
return function (e) {
|
|
1205
|
+
e.stopPropagation();
|
|
1206
|
+
if (expandedMobileLoopRuns.has(rk)) {
|
|
1207
|
+
expandedMobileLoopRuns.delete(rk);
|
|
1208
|
+
} else {
|
|
1209
|
+
expandedMobileLoopRuns.add(rk);
|
|
1210
|
+
}
|
|
1211
|
+
refreshMobileChatSheet();
|
|
1212
|
+
};
|
|
1213
|
+
})(runGk));
|
|
1214
|
+
|
|
1215
|
+
wrapper.appendChild(header);
|
|
1216
|
+
|
|
1217
|
+
if (expanded) {
|
|
1218
|
+
var childContainer = document.createElement("div");
|
|
1219
|
+
childContainer.className = "mobile-loop-children";
|
|
1220
|
+
for (var k = 0; k < sessions.length; k++) {
|
|
1221
|
+
childContainer.appendChild(createMobileLoopChild(sessions[k]));
|
|
1222
|
+
}
|
|
1223
|
+
wrapper.appendChild(childContainer);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
return wrapper;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Helper: create a mobile loop group element (collapsible group header)
|
|
1230
|
+
function createMobileLoopGroup(loopId, children, groupKey) {
|
|
1231
|
+
var gk = groupKey || loopId;
|
|
1232
|
+
|
|
1233
|
+
// Sub-group children by startedAt (each run)
|
|
1234
|
+
var runMap = {};
|
|
1235
|
+
for (var i = 0; i < children.length; i++) {
|
|
1236
|
+
var runKey = String(children[i].loop && children[i].loop.startedAt || 0);
|
|
1237
|
+
if (!runMap[runKey]) runMap[runKey] = [];
|
|
1238
|
+
runMap[runKey].push(children[i]);
|
|
1239
|
+
}
|
|
1240
|
+
var runKeys = Object.keys(runMap);
|
|
1241
|
+
|
|
1242
|
+
// Sort each run's children by iteration then role
|
|
1243
|
+
for (var ri = 0; ri < runKeys.length; ri++) {
|
|
1244
|
+
runMap[runKeys[ri]].sort(function (a, b) {
|
|
1245
|
+
var ai = (a.loop && a.loop.iteration) || 0;
|
|
1246
|
+
var bi = (b.loop && b.loop.iteration) || 0;
|
|
1247
|
+
if (ai !== bi) return ai - bi;
|
|
1248
|
+
var ar = (a.loop && a.loop.role === "judge") ? 1 : 0;
|
|
1249
|
+
var br = (b.loop && b.loop.role === "judge") ? 1 : 0;
|
|
1250
|
+
return ar - br;
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Sort runs by startedAt descending (newest first)
|
|
1255
|
+
runKeys.sort(function (a, b) { return Number(b) - Number(a); });
|
|
1256
|
+
|
|
1257
|
+
var expanded = expandedMobileLoopGroups.has(gk);
|
|
1258
|
+
var hasActive = false;
|
|
1259
|
+
var anyProcessing = false;
|
|
1260
|
+
var latestSession = children[0];
|
|
1261
|
+
for (var ci = 0; ci < children.length; ci++) {
|
|
1262
|
+
if (children[ci].active) hasActive = true;
|
|
1263
|
+
if (children[ci].isProcessing) anyProcessing = true;
|
|
1264
|
+
if ((children[ci].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
1265
|
+
latestSession = children[ci];
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
var loopName = (children[0].loop && children[0].loop.name) || "Ralph Loop";
|
|
1270
|
+
var isRalph = children[0].loop && children[0].loop.source === "ralph";
|
|
1271
|
+
var isCrafting = false;
|
|
1272
|
+
for (var j = 0; j < children.length; j++) {
|
|
1273
|
+
if (children[j].loop && children[j].loop.role === "crafting") isCrafting = true;
|
|
1274
|
+
}
|
|
1275
|
+
var runCount = runKeys.length;
|
|
1276
|
+
|
|
1277
|
+
var wrapper = document.createElement("div");
|
|
1278
|
+
wrapper.className = "mobile-loop-wrapper";
|
|
1279
|
+
|
|
1280
|
+
// Group header row
|
|
1281
|
+
var header = document.createElement("button");
|
|
1282
|
+
header.className = "mobile-loop-group" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
|
|
1283
|
+
|
|
1284
|
+
var chevron = document.createElement("span");
|
|
1285
|
+
chevron.className = "mobile-loop-chevron";
|
|
1286
|
+
chevron.innerHTML = iconHtml("chevron-right");
|
|
1287
|
+
header.appendChild(chevron);
|
|
1288
|
+
|
|
1289
|
+
var iconSpan = document.createElement("span");
|
|
1290
|
+
var groupIcon = isRalph ? "repeat" : "calendar-clock";
|
|
1291
|
+
iconSpan.className = "mobile-loop-icon" + (isRalph ? "" : " scheduled");
|
|
1292
|
+
iconSpan.innerHTML = iconHtml(groupIcon);
|
|
1293
|
+
header.appendChild(iconSpan);
|
|
1294
|
+
|
|
1295
|
+
if (anyProcessing) {
|
|
1296
|
+
var dot = document.createElement("span");
|
|
1297
|
+
dot.className = "mobile-session-processing";
|
|
1298
|
+
header.appendChild(dot);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
var nameSpan = document.createElement("span");
|
|
1302
|
+
nameSpan.className = "mobile-loop-name";
|
|
1303
|
+
nameSpan.textContent = loopName;
|
|
1304
|
+
header.appendChild(nameSpan);
|
|
1305
|
+
|
|
1306
|
+
if (isCrafting && children.length === 1) {
|
|
1307
|
+
var craftBadge = document.createElement("span");
|
|
1308
|
+
craftBadge.className = "mobile-loop-badge crafting";
|
|
1309
|
+
craftBadge.textContent = "Crafting";
|
|
1310
|
+
header.appendChild(craftBadge);
|
|
1311
|
+
} else {
|
|
1312
|
+
var countBadge = document.createElement("span");
|
|
1313
|
+
countBadge.className = "mobile-loop-count" + (isRalph ? "" : " scheduled");
|
|
1314
|
+
var countLabel = runCount === 1 ? String(children.length) : runCount + (runCount === 1 ? " run" : " runs");
|
|
1315
|
+
countBadge.textContent = countLabel;
|
|
1316
|
+
header.appendChild(countBadge);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Chevron toggles expansion
|
|
1320
|
+
header.addEventListener("click", (function (lid) {
|
|
1321
|
+
return function (e) {
|
|
1322
|
+
e.stopPropagation();
|
|
1323
|
+
if (expandedMobileLoopGroups.has(lid)) {
|
|
1324
|
+
expandedMobileLoopGroups.delete(lid);
|
|
1325
|
+
} else {
|
|
1326
|
+
expandedMobileLoopGroups.add(lid);
|
|
1327
|
+
}
|
|
1328
|
+
refreshMobileChatSheet();
|
|
1329
|
+
};
|
|
1330
|
+
})(gk));
|
|
1331
|
+
|
|
1332
|
+
wrapper.appendChild(header);
|
|
1333
|
+
|
|
1334
|
+
// Expanded: show runs
|
|
1335
|
+
if (expanded) {
|
|
1336
|
+
var childContainer = document.createElement("div");
|
|
1337
|
+
childContainer.className = "mobile-loop-children";
|
|
1338
|
+
|
|
1339
|
+
if (runCount === 1) {
|
|
1340
|
+
var singleRun = runMap[runKeys[0]];
|
|
1341
|
+
for (var sk = 0; sk < singleRun.length; sk++) {
|
|
1342
|
+
childContainer.appendChild(createMobileLoopChild(singleRun[sk]));
|
|
1343
|
+
}
|
|
1344
|
+
} else {
|
|
1345
|
+
for (var rk = 0; rk < runKeys.length; rk++) {
|
|
1346
|
+
childContainer.appendChild(createMobileLoopRun(gk, runKeys[rk], runMap[runKeys[rk]], isRalph));
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
wrapper.appendChild(childContainer);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
return wrapper;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Helper: render sorted sessions into a container with date groups (with loop session grouping)
|
|
1108
1357
|
function renderMobileSessionsInto(container) {
|
|
1109
1358
|
var newBtn = document.createElement("button");
|
|
1110
1359
|
newBtn.className = "mobile-session-new";
|
|
@@ -1117,14 +1366,51 @@ function renderMobileSessionsInto(container) {
|
|
|
1117
1366
|
});
|
|
1118
1367
|
container.appendChild(newBtn);
|
|
1119
1368
|
|
|
1120
|
-
|
|
1369
|
+
// Partition: loop sessions vs normal sessions (same logic as desktop renderSessionList)
|
|
1370
|
+
var loopGroups = {};
|
|
1371
|
+
var normalSessions = [];
|
|
1372
|
+
for (var i = 0; i < cachedSessions.length; i++) {
|
|
1373
|
+
var s = cachedSessions[i];
|
|
1374
|
+
if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph") {
|
|
1375
|
+
continue;
|
|
1376
|
+
} else if (s.loop && s.loop.loopId) {
|
|
1377
|
+
var startedAt = s.loop.startedAt || 0;
|
|
1378
|
+
var dateStr = startedAt ? new Date(startedAt).toISOString().slice(0, 10) : "unknown";
|
|
1379
|
+
var groupKey = s.loop.loopId + ":" + dateStr;
|
|
1380
|
+
if (!loopGroups[groupKey]) loopGroups[groupKey] = [];
|
|
1381
|
+
loopGroups[groupKey].push(s);
|
|
1382
|
+
} else {
|
|
1383
|
+
normalSessions.push(s);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Build virtual items
|
|
1388
|
+
var items = [];
|
|
1389
|
+
for (var j = 0; j < normalSessions.length; j++) {
|
|
1390
|
+
items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
|
|
1391
|
+
}
|
|
1392
|
+
var groupKeys = Object.keys(loopGroups);
|
|
1393
|
+
for (var k = 0; k < groupKeys.length; k++) {
|
|
1394
|
+
var gk = groupKeys[k];
|
|
1395
|
+
var children = loopGroups[gk];
|
|
1396
|
+
var realLoopId = children[0].loop.loopId;
|
|
1397
|
+
var maxActivity = 0;
|
|
1398
|
+
for (var m = 0; m < children.length; m++) {
|
|
1399
|
+
var act = children[m].lastActivity || 0;
|
|
1400
|
+
if (act > maxActivity) maxActivity = act;
|
|
1401
|
+
}
|
|
1402
|
+
items.push({ type: "loop", loopId: realLoopId, groupKey: gk, children: children, lastActivity: maxActivity });
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Sort by lastActivity descending
|
|
1406
|
+
items.sort(function (a, b) {
|
|
1121
1407
|
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
1122
1408
|
});
|
|
1123
1409
|
|
|
1124
1410
|
var currentGroup = "";
|
|
1125
|
-
for (var
|
|
1126
|
-
var
|
|
1127
|
-
var group = getDateGroup(
|
|
1411
|
+
for (var n = 0; n < items.length; n++) {
|
|
1412
|
+
var item = items[n];
|
|
1413
|
+
var group = getDateGroup(item.lastActivity || 0);
|
|
1128
1414
|
if (group !== currentGroup) {
|
|
1129
1415
|
currentGroup = group;
|
|
1130
1416
|
var header = document.createElement("div");
|
|
@@ -1132,7 +1418,11 @@ function renderMobileSessionsInto(container) {
|
|
|
1132
1418
|
header.textContent = group;
|
|
1133
1419
|
container.appendChild(header);
|
|
1134
1420
|
}
|
|
1135
|
-
|
|
1421
|
+
if (item.type === "loop") {
|
|
1422
|
+
container.appendChild(createMobileLoopGroup(item.loopId, item.children, item.groupKey));
|
|
1423
|
+
} else {
|
|
1424
|
+
container.appendChild(createMobileSessionItem(item.data));
|
|
1425
|
+
}
|
|
1136
1426
|
}
|
|
1137
1427
|
}
|
|
1138
1428
|
|
|
@@ -2141,15 +2431,13 @@ function showMateCtxMenu(anchorEl, mate) {
|
|
|
2141
2431
|
menu.appendChild(editItem);
|
|
2142
2432
|
|
|
2143
2433
|
var removeItem = document.createElement("button");
|
|
2144
|
-
removeItem.className = "project-ctx-item
|
|
2145
|
-
removeItem.innerHTML = iconHtml("
|
|
2434
|
+
removeItem.className = "project-ctx-item";
|
|
2435
|
+
removeItem.innerHTML = iconHtml("star-off") + " <span>Remove from favorites</span>";
|
|
2146
2436
|
removeItem.addEventListener("click", function (e) {
|
|
2147
2437
|
e.stopPropagation();
|
|
2148
|
-
var iconRect = anchorEl.getBoundingClientRect();
|
|
2149
|
-
spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
|
|
2150
2438
|
closeUserCtxMenu();
|
|
2151
2439
|
if (ctx.sendWs) {
|
|
2152
|
-
ctx.sendWs({ type: "
|
|
2440
|
+
ctx.sendWs({ type: "dm_remove_favorite", targetUserId: mate.id });
|
|
2153
2441
|
}
|
|
2154
2442
|
});
|
|
2155
2443
|
menu.appendChild(removeItem);
|
|
@@ -3522,8 +3810,20 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
|
|
|
3522
3810
|
}
|
|
3523
3811
|
}
|
|
3524
3812
|
|
|
3525
|
-
// Render mates
|
|
3526
|
-
|
|
3813
|
+
// Render mates (only favorites, built-in first, then user-created)
|
|
3814
|
+
var favoriteMates = cachedMates.filter(function (m) {
|
|
3815
|
+
if (cachedDmRemovedUsers[m.id]) return false;
|
|
3816
|
+
if (cachedDmFavorites.indexOf(m.id) !== -1) return true;
|
|
3817
|
+
if (cachedDmUnread[m.id] && cachedDmUnread[m.id] > 0) return true;
|
|
3818
|
+
return false;
|
|
3819
|
+
});
|
|
3820
|
+
var sortedMates = favoriteMates.sort(function (a, b) {
|
|
3821
|
+
var aBuiltin = a.builtinKey ? 1 : 0;
|
|
3822
|
+
var bBuiltin = b.builtinKey ? 1 : 0;
|
|
3823
|
+
if (aBuiltin !== bBuiltin) return bBuiltin - aBuiltin;
|
|
3824
|
+
return (a.createdAt || 0) - (b.createdAt || 0);
|
|
3825
|
+
});
|
|
3826
|
+
for (var mi = 0; mi < sortedMates.length; mi++) {
|
|
3527
3827
|
(function (mate) {
|
|
3528
3828
|
var mp = mate.profile || {};
|
|
3529
3829
|
var mateSlug = "mate-" + mate.id;
|
|
@@ -3598,7 +3898,7 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
|
|
|
3598
3898
|
});
|
|
3599
3899
|
|
|
3600
3900
|
container.appendChild(el);
|
|
3601
|
-
})(
|
|
3901
|
+
})(sortedMates[mi]);
|
|
3602
3902
|
}
|
|
3603
3903
|
|
|
3604
3904
|
// Show container if we have mates even with no other users
|
|
@@ -3635,13 +3935,12 @@ function toggleDmUserPicker(anchorEl) {
|
|
|
3635
3935
|
var searchInput = document.createElement("input");
|
|
3636
3936
|
searchInput.className = "dm-user-picker-search";
|
|
3637
3937
|
searchInput.type = "text";
|
|
3638
|
-
searchInput.placeholder = "Search users...";
|
|
3938
|
+
searchInput.placeholder = "Search mates and users...";
|
|
3639
3939
|
picker.appendChild(searchInput);
|
|
3640
3940
|
|
|
3641
|
-
//
|
|
3941
|
+
// User list element (appended later, after USERS label)
|
|
3642
3942
|
var listEl = document.createElement("div");
|
|
3643
3943
|
listEl.className = "dm-user-picker-list";
|
|
3644
|
-
picker.appendChild(listEl);
|
|
3645
3944
|
|
|
3646
3945
|
// Position the picker above the + button
|
|
3647
3946
|
document.body.appendChild(picker);
|
|
@@ -3698,10 +3997,193 @@ function toggleDmUserPicker(anchorEl) {
|
|
|
3698
3997
|
}
|
|
3699
3998
|
}
|
|
3700
3999
|
|
|
4000
|
+
// --- MATES section ---
|
|
4001
|
+
var matesSectionLabel = document.createElement("div");
|
|
4002
|
+
matesSectionLabel.className = "dm-user-picker-section";
|
|
4003
|
+
matesSectionLabel.textContent = "Mates";
|
|
4004
|
+
picker.appendChild(matesSectionLabel);
|
|
4005
|
+
|
|
4006
|
+
var matesListEl = document.createElement("div");
|
|
4007
|
+
matesListEl.className = "dm-user-picker-list dm-mates-list";
|
|
4008
|
+
picker.appendChild(matesListEl);
|
|
4009
|
+
|
|
4010
|
+
// Update scroll gradient hint
|
|
4011
|
+
function updateMatesScrollHint() {
|
|
4012
|
+
var isOverflow = matesListEl.scrollHeight > matesListEl.clientHeight + 2;
|
|
4013
|
+
if (!isOverflow) {
|
|
4014
|
+
matesListEl.classList.add("no-overflow");
|
|
4015
|
+
matesListEl.classList.remove("scrolled-bottom");
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
matesListEl.classList.remove("no-overflow");
|
|
4019
|
+
var atBottom = matesListEl.scrollTop + matesListEl.clientHeight >= matesListEl.scrollHeight - 4;
|
|
4020
|
+
if (atBottom) {
|
|
4021
|
+
matesListEl.classList.add("scrolled-bottom");
|
|
4022
|
+
} else {
|
|
4023
|
+
matesListEl.classList.remove("scrolled-bottom");
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
matesListEl.addEventListener("scroll", updateMatesScrollHint);
|
|
4027
|
+
|
|
4028
|
+
function renderMatesList(filter) {
|
|
4029
|
+
matesListEl.innerHTML = "";
|
|
4030
|
+
var allMates = cachedMates || [];
|
|
4031
|
+
if (filter) {
|
|
4032
|
+
var lf = filter.toLowerCase();
|
|
4033
|
+
allMates = allMates.filter(function (m) {
|
|
4034
|
+
var name = (m.profile && m.profile.displayName) || m.name || "";
|
|
4035
|
+
return name.toLowerCase().indexOf(lf) !== -1;
|
|
4036
|
+
});
|
|
4037
|
+
}
|
|
4038
|
+
// Build unified list: installed builtins, deleted builtins, user-created
|
|
4039
|
+
var availBuiltins = (ctx.availableBuiltins && ctx.availableBuiltins()) || [];
|
|
4040
|
+
var entries = [];
|
|
4041
|
+
// 1. Installed builtin mates
|
|
4042
|
+
for (var si = 0; si < allMates.length; si++) {
|
|
4043
|
+
if (allMates[si].builtinKey) entries.push({ type: "mate", data: allMates[si] });
|
|
4044
|
+
}
|
|
4045
|
+
// 2. Deleted builtins (only when not filtering)
|
|
4046
|
+
if (!filter) {
|
|
4047
|
+
for (var di = 0; di < availBuiltins.length; di++) {
|
|
4048
|
+
entries.push({ type: "deleted", data: availBuiltins[di] });
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
// 3. User-created mates
|
|
4052
|
+
var userMates = allMates.filter(function (m) { return !m.builtinKey; });
|
|
4053
|
+
userMates.sort(function (a, b) { return (a.createdAt || 0) - (b.createdAt || 0); });
|
|
4054
|
+
for (var ui = 0; ui < userMates.length; ui++) {
|
|
4055
|
+
entries.push({ type: "mate", data: userMates[ui] });
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4058
|
+
for (var i = 0; i < entries.length; i++) {
|
|
4059
|
+
var entry = entries[i];
|
|
4060
|
+
if (entry.type === "deleted") {
|
|
4061
|
+
// Deleted builtin: show with "+ Add" button
|
|
4062
|
+
(function (b) {
|
|
4063
|
+
var bItem = document.createElement("div");
|
|
4064
|
+
bItem.className = "dm-user-picker-item dm-user-picker-builtin-item";
|
|
4065
|
+
bItem.style.opacity = "0.5";
|
|
4066
|
+
var bAv = document.createElement("img");
|
|
4067
|
+
bAv.className = "dm-user-picker-avatar";
|
|
4068
|
+
bAv.src = b.avatarCustom || "";
|
|
4069
|
+
bAv.alt = b.displayName;
|
|
4070
|
+
bItem.appendChild(bAv);
|
|
4071
|
+
var bNameWrap = document.createElement("div");
|
|
4072
|
+
bNameWrap.style.cssText = "flex:1;min-width:0;";
|
|
4073
|
+
var bName = document.createElement("span");
|
|
4074
|
+
bName.className = "dm-user-picker-name";
|
|
4075
|
+
bName.textContent = b.displayName;
|
|
4076
|
+
bNameWrap.appendChild(bName);
|
|
4077
|
+
var bBio = document.createElement("div");
|
|
4078
|
+
bBio.style.cssText = "font-size:11px;color:var(--text-dimmer);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
4079
|
+
bBio.textContent = "Deleted";
|
|
4080
|
+
bNameWrap.appendChild(bBio);
|
|
4081
|
+
bItem.appendChild(bNameWrap);
|
|
4082
|
+
var bAddBtn = document.createElement("button");
|
|
4083
|
+
bAddBtn.style.cssText = "border:none;background:none;cursor:pointer;padding:2px 6px;color:var(--accent, #6366f1);font-size:12px;font-weight:600;white-space:nowrap;";
|
|
4084
|
+
bAddBtn.textContent = "+ Add";
|
|
4085
|
+
bAddBtn.title = "Re-add " + b.displayName;
|
|
4086
|
+
bAddBtn.addEventListener("click", function (e) {
|
|
4087
|
+
e.stopPropagation();
|
|
4088
|
+
if (ctx.sendWs) ctx.sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
|
|
4089
|
+
closeDmUserPicker();
|
|
4090
|
+
});
|
|
4091
|
+
bItem.appendChild(bAddBtn);
|
|
4092
|
+
bItem.addEventListener("click", function () {
|
|
4093
|
+
if (ctx.sendWs) ctx.sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
|
|
4094
|
+
closeDmUserPicker();
|
|
4095
|
+
});
|
|
4096
|
+
matesListEl.appendChild(bItem);
|
|
4097
|
+
})(entry.data);
|
|
4098
|
+
} else {
|
|
4099
|
+
// Normal mate
|
|
4100
|
+
(function (m) {
|
|
4101
|
+
var mp = m.profile || {};
|
|
4102
|
+
var isFav = cachedDmFavorites.indexOf(m.id) !== -1;
|
|
4103
|
+
var item = document.createElement("div");
|
|
4104
|
+
item.className = "dm-user-picker-item";
|
|
4105
|
+
if (isFav) item.classList.add("dm-picker-fav");
|
|
4106
|
+
var av = document.createElement("img");
|
|
4107
|
+
av.className = "dm-user-picker-avatar";
|
|
4108
|
+
av.src = mateAvatarUrl(m, 28);
|
|
4109
|
+
av.alt = mp.displayName || m.name || "Mate";
|
|
4110
|
+
item.appendChild(av);
|
|
4111
|
+
var nameWrap = document.createElement("div");
|
|
4112
|
+
nameWrap.style.cssText = "flex:1;min-width:0;";
|
|
4113
|
+
var name = document.createElement("span");
|
|
4114
|
+
name.className = "dm-user-picker-name";
|
|
4115
|
+
name.textContent = mp.displayName || m.name || "Mate";
|
|
4116
|
+
nameWrap.appendChild(name);
|
|
4117
|
+
if (m.bio) {
|
|
4118
|
+
var bio = document.createElement("div");
|
|
4119
|
+
bio.style.cssText = "font-size:11px;color:var(--text-dimmer);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
4120
|
+
bio.textContent = m.bio;
|
|
4121
|
+
nameWrap.appendChild(bio);
|
|
4122
|
+
}
|
|
4123
|
+
item.appendChild(nameWrap);
|
|
4124
|
+
// Delete button with inline confirm
|
|
4125
|
+
var delBtn = document.createElement("button");
|
|
4126
|
+
delBtn.className = "dm-picker-del-btn";
|
|
4127
|
+
delBtn.innerHTML = iconHtml("trash-2");
|
|
4128
|
+
delBtn.title = "Delete mate";
|
|
4129
|
+
delBtn.addEventListener("click", function (e) {
|
|
4130
|
+
e.stopPropagation();
|
|
4131
|
+
var origHtml = item.innerHTML;
|
|
4132
|
+
item.innerHTML = "";
|
|
4133
|
+
item.style.justifyContent = "center";
|
|
4134
|
+
item.style.gap = "6px";
|
|
4135
|
+
var confirmMsg = document.createElement("span");
|
|
4136
|
+
confirmMsg.style.cssText = "font-size:12px;color:var(--text-dimmer);";
|
|
4137
|
+
confirmMsg.textContent = m.builtinKey ? "Delete? You can re-add later." : "Delete permanently?";
|
|
4138
|
+
item.appendChild(confirmMsg);
|
|
4139
|
+
var yesBtn = document.createElement("button");
|
|
4140
|
+
yesBtn.style.cssText = "border:none;background:var(--danger,#e74c3c);color:#fff;padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;";
|
|
4141
|
+
yesBtn.textContent = "Delete";
|
|
4142
|
+
yesBtn.addEventListener("click", function (e2) {
|
|
4143
|
+
e2.stopPropagation();
|
|
4144
|
+
if (ctx.sendWs) ctx.sendWs({ type: "mate_delete", mateId: m.id });
|
|
4145
|
+
closeDmUserPicker();
|
|
4146
|
+
});
|
|
4147
|
+
item.appendChild(yesBtn);
|
|
4148
|
+
var noBtn = document.createElement("button");
|
|
4149
|
+
noBtn.style.cssText = "border:1px solid var(--border);background:none;color:var(--text);padding:3px 10px;border-radius:4px;font-size:12px;cursor:pointer;";
|
|
4150
|
+
noBtn.textContent = "Cancel";
|
|
4151
|
+
noBtn.addEventListener("click", function (e2) {
|
|
4152
|
+
e2.stopPropagation();
|
|
4153
|
+
item.innerHTML = origHtml;
|
|
4154
|
+
item.style.justifyContent = "";
|
|
4155
|
+
item.style.gap = "";
|
|
4156
|
+
refreshIcons();
|
|
4157
|
+
});
|
|
4158
|
+
item.appendChild(noBtn);
|
|
4159
|
+
});
|
|
4160
|
+
item.appendChild(delBtn);
|
|
4161
|
+
item.addEventListener("click", function () {
|
|
4162
|
+
if (ctx.openDm) ctx.openDm(m.id);
|
|
4163
|
+
if (!isFav && ctx.sendWs) ctx.sendWs({ type: "dm_add_favorite", targetUserId: m.id });
|
|
4164
|
+
closeDmUserPicker();
|
|
4165
|
+
});
|
|
4166
|
+
matesListEl.appendChild(item);
|
|
4167
|
+
})(entry.data);
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
if (entries.length === 0 && filter) {
|
|
4172
|
+
var emptyEl = document.createElement("div");
|
|
4173
|
+
emptyEl.className = "dm-user-picker-empty";
|
|
4174
|
+
emptyEl.textContent = "No mates found";
|
|
4175
|
+
matesListEl.appendChild(emptyEl);
|
|
4176
|
+
}
|
|
4177
|
+
refreshIcons();
|
|
4178
|
+
requestAnimationFrame(updateMatesScrollHint);
|
|
4179
|
+
}
|
|
4180
|
+
|
|
3701
4181
|
// Create Mate option
|
|
3702
4182
|
var createMateEl = document.createElement("div");
|
|
3703
4183
|
createMateEl.className = "dm-user-picker-create-mate";
|
|
3704
|
-
|
|
4184
|
+
var hasCustomMates = (cachedMates || []).some(function (m) { return !m.builtinKey; });
|
|
4185
|
+
var createMateLabel = hasCustomMates ? "Create a Mate" : "Create a Mate for what you're doing";
|
|
4186
|
+
createMateEl.innerHTML = iconHtml("bot") + " <span>" + createMateLabel + "</span>";
|
|
3705
4187
|
createMateEl.addEventListener("click", function () {
|
|
3706
4188
|
closeDmUserPicker();
|
|
3707
4189
|
if (ctx.openMateWizard) ctx.openMateWizard();
|
|
@@ -3719,10 +4201,14 @@ function toggleDmUserPicker(anchorEl) {
|
|
|
3719
4201
|
sectionLabel.className = "dm-user-picker-section";
|
|
3720
4202
|
sectionLabel.textContent = "Users";
|
|
3721
4203
|
picker.appendChild(sectionLabel);
|
|
4204
|
+
picker.appendChild(listEl);
|
|
3722
4205
|
|
|
4206
|
+
renderMatesList("");
|
|
3723
4207
|
renderPickerList("");
|
|
3724
4208
|
searchInput.addEventListener("input", function () {
|
|
3725
|
-
|
|
4209
|
+
var val = searchInput.value;
|
|
4210
|
+
renderMatesList(val);
|
|
4211
|
+
renderPickerList(val);
|
|
3726
4212
|
});
|
|
3727
4213
|
|
|
3728
4214
|
// Focus search
|
|
@@ -495,13 +495,16 @@ export function toggleDarkMode() {
|
|
|
495
495
|
updateToggleIcon();
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
// Update
|
|
498
|
+
// Update all theme toggle checkboxes
|
|
499
499
|
function updateToggleIcon() {
|
|
500
|
-
var checkbox = document.getElementById("theme-toggle-check");
|
|
501
|
-
if (!checkbox) return;
|
|
502
500
|
var mode = getEffectiveMode();
|
|
503
|
-
|
|
504
|
-
|
|
501
|
+
var isLight = mode === "light";
|
|
502
|
+
// Legacy top-bar toggle (may not exist)
|
|
503
|
+
var checkbox = document.getElementById("theme-toggle-check");
|
|
504
|
+
if (checkbox) checkbox.checked = isLight;
|
|
505
|
+
// User settings toggle
|
|
506
|
+
var usToggle = document.getElementById("us-theme-toggle");
|
|
507
|
+
if (usToggle) usToggle.checked = isLight;
|
|
505
508
|
}
|
|
506
509
|
|
|
507
510
|
// --- Theme picker UI ---
|
|
@@ -671,13 +674,8 @@ export function initTheme() {
|
|
|
671
674
|
// Load all themes from server, then apply properly
|
|
672
675
|
loadThemes();
|
|
673
676
|
|
|
674
|
-
//
|
|
675
|
-
|
|
676
|
-
if (toggleCheck) {
|
|
677
|
-
toggleCheck.addEventListener("change", function () {
|
|
678
|
-
toggleDarkMode();
|
|
679
|
-
});
|
|
680
|
-
}
|
|
677
|
+
// Theme toggle is now in User Settings (us-theme-toggle)
|
|
678
|
+
// Wired up in user-settings.js initUserSettings()
|
|
681
679
|
|
|
682
680
|
// Listen for system preference changes (only applies if user has no manual override)
|
|
683
681
|
if (window.matchMedia) {
|