+116
-89
templates/status.html
+116
-89
templates/status.html
···
470
470
471
471
body.modal-open {
472
472
overflow: hidden;
473
+
position: fixed;
474
+
width: 100%;
473
475
}
474
476
475
477
:root {
···
1066
1068
/* Emoji Picker */
1067
1069
.emoji-picker-overlay {
1068
1070
position: fixed;
1071
+
top: 0;
1072
+
right: 0;
1073
+
bottom: 0;
1074
+
left: 0;
1069
1075
inset: 0;
1070
1076
background: rgba(6, 6, 8, 0.75);
1071
1077
backdrop-filter: blur(6px);
···
1175
1181
overflow-x: auto;
1176
1182
padding-bottom: 0.5rem;
1177
1183
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
1184
+
scrollbar-width: none;
1178
1185
}
1179
1186
1180
1187
.emoji-categories.hidden {
1188
+
display: none;
1189
+
}
1190
+
1191
+
.emoji-categories::-webkit-scrollbar {
1181
1192
display: none;
1182
1193
}
1183
1194
···
1209
1220
.emoji-grid {
1210
1221
flex: 1;
1211
1222
display: grid;
1212
-
grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
1223
+
grid-template-columns: repeat(auto-fill, minmax(min(84px, 18vw), 1fr));
1213
1224
gap: 0.75rem;
1214
1225
padding-right: 0.25rem;
1215
1226
overflow-y: auto;
1227
+
scrollbar-width: none;
1228
+
}
1229
+
1230
+
.emoji-grid::-webkit-scrollbar {
1231
+
display: none;
1216
1232
}
1217
1233
1218
1234
.emoji-option {
···
1244
1260
object-fit: contain;
1245
1261
}
1246
1262
1263
+
.emoji-picker-overlay::before {
1264
+
content: '';
1265
+
position: fixed;
1266
+
top: 0;
1267
+
right: 0;
1268
+
bottom: 0;
1269
+
left: 0;
1270
+
}
1271
+
1247
1272
@media (max-width: 640px) {
1248
1273
.emoji-picker-overlay {
1249
1274
padding: 0;
···
1262
1287
.emoji-grid {
1263
1288
grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
1264
1289
gap: 0.5rem;
1290
+
}
1291
+
}
1292
+
1293
+
@media (prefers-reduced-motion: reduce) {
1294
+
.emoji-picker-overlay {
1295
+
backdrop-filter: none;
1296
+
background: rgba(6, 6, 8, 0.85);
1297
+
}
1298
+
1299
+
.emoji-picker,
1300
+
.emoji-option {
1301
+
transition: none;
1265
1302
}
1266
1303
}
1267
1304
···
2051
2088
const emojiGrid = document.getElementById('emoji-grid');
2052
2089
const selectedEmoji = document.getElementById('selected-emoji');
2053
2090
const statusInput = document.getElementById('status-input');
2091
+
const emojiSearch = document.getElementById('emoji-search');
2092
+
const emojiCategories = document.getElementById('emoji-categories');
2054
2093
let lastFocusBeforeEmojiPicker = null;
2055
-
2094
+
let scrollPosition = 0;
2095
+
let isScrollLocked = false;
2096
+
let keydownBound = false;
2097
+
2056
2098
// Clear time picker
2057
2099
const clearAfterBtn = document.getElementById('clear-after-btn');
2058
2100
const clearPicker = document.getElementById('clear-picker');
2059
2101
const clearText = document.getElementById('clear-text');
2060
2102
const expiresSelect = document.getElementById('expires_in');
2061
-
2103
+
2062
2104
const isEmojiPickerOpen = () => emojiPickerOverlay && !emojiPickerOverlay.classList.contains('hidden');
2063
2105
2106
+
const handleKeydown = (e) => {
2107
+
if (e.key === 'Escape' && isEmojiPickerOpen()) {
2108
+
closeEmojiPicker();
2109
+
}
2110
+
};
2111
+
2064
2112
const openEmojiPicker = () => {
2065
2113
if (!emojiPickerOverlay || !emojiPicker) return;
2066
2114
2067
2115
lastFocusBeforeEmojiPicker = document.activeElement;
2116
+
scrollPosition = window.scrollY || document.documentElement.scrollTop || 0;
2068
2117
2069
2118
emojiPickerOverlay.classList.remove('hidden');
2070
2119
emojiPickerOverlay.setAttribute('aria-hidden', 'false');
2071
2120
document.body.classList.add('modal-open');
2121
+
document.body.style.top = `-${scrollPosition}px`;
2122
+
isScrollLocked = true;
2072
2123
if (clearPicker) clearPicker.style.display = 'none';
2073
2124
2074
-
const emojiSearch = document.getElementById('emoji-search');
2075
2125
if (emojiSearch) {
2076
2126
emojiSearch.value = '';
2077
-
const categories = document.getElementById('emoji-categories');
2078
-
if (categories) categories.classList.remove('hidden');
2079
-
setTimeout(() => {
2080
-
if (emojiSearch) emojiSearch.focus();
2081
-
}, 60);
2127
+
if (emojiCategories) emojiCategories.classList.remove('hidden');
2128
+
requestAnimationFrame(() => {
2129
+
try { emojiSearch.focus(); } catch (_) {}
2130
+
});
2082
2131
}
2083
2132
2084
2133
loadCustomEmojis().then(() => {
2085
2134
loadEmojiCategory('frequent');
2086
2135
});
2136
+
2137
+
if (!keydownBound) {
2138
+
document.addEventListener('keydown', handleKeydown);
2139
+
keydownBound = true;
2140
+
}
2087
2141
};
2088
2142
2089
2143
const closeEmojiPicker = () => {
···
2091
2145
emojiPickerOverlay.classList.add('hidden');
2092
2146
emojiPickerOverlay.setAttribute('aria-hidden', 'true');
2093
2147
document.body.classList.remove('modal-open');
2148
+
document.body.style.top = '';
2149
+
if (isScrollLocked) {
2150
+
window.scrollTo(0, scrollPosition);
2151
+
}
2152
+
scrollPosition = 0;
2153
+
isScrollLocked = false;
2094
2154
if (lastFocusBeforeEmojiPicker && typeof lastFocusBeforeEmojiPicker.focus === 'function') {
2095
2155
const focusTarget = lastFocusBeforeEmojiPicker;
2096
-
setTimeout(() => {
2156
+
requestAnimationFrame(() => {
2097
2157
try { focusTarget.focus(); } catch (_) {}
2098
-
}, 50);
2158
+
});
2099
2159
}
2100
2160
lastFocusBeforeEmojiPicker = null;
2161
+
2162
+
if (keydownBound) {
2163
+
document.removeEventListener('keydown', handleKeydown);
2164
+
keydownBound = false;
2165
+
}
2101
2166
};
2102
2167
2103
2168
// Show emoji picker
···
2127
2192
}
2128
2193
});
2129
2194
}
2130
-
2131
-
document.addEventListener('keydown', (e) => {
2132
-
if (e.key === 'Escape' && isEmojiPickerOpen()) {
2133
-
closeEmojiPicker();
2134
-
}
2135
-
});
2136
2195
2137
2196
// Store custom emojis
2138
2197
let customEmojis = [];
···
2187
2246
}
2188
2247
2189
2248
emojiGrid.innerHTML = html;
2190
-
2191
-
// Add click handlers for both types
2192
-
emojiGrid.querySelectorAll('.emoji-option').forEach(btn => {
2193
-
btn.addEventListener('click', (e) => {
2194
-
e.preventDefault();
2195
-
const emoji = btn.getAttribute('data-emoji');
2196
-
2197
-
if (emoji.startsWith('custom:')) {
2198
-
const img = btn.querySelector('img');
2199
-
selectedEmoji.innerHTML = `<img src="${img.src}" alt="${img.alt}" style="width: 100%; height: 100%; object-fit: contain;">`;
2200
-
} else {
2201
-
selectedEmoji.textContent = emoji;
2202
-
}
2203
-
2204
-
statusInput.value = emoji;
2205
-
closeEmojiPicker();
2206
-
checkForChanges();
2207
-
});
2208
-
});
2209
2249
} else if (category === 'custom') {
2210
2250
// Load custom image emojis
2211
2251
if (customEmojis.length === 0) {
···
2217
2257
<img src="/emojis/${emoji.filename}" alt="${emoji.name}" title="${emoji.name}">
2218
2258
</button>`
2219
2259
).join('');
2220
-
2221
-
// Add click handlers for custom emojis
2222
-
emojiGrid.querySelectorAll('.custom-emoji').forEach(btn => {
2223
-
btn.addEventListener('click', (e) => {
2224
-
e.preventDefault();
2225
-
const emojiName = btn.getAttribute('data-name');
2226
-
const emojiValue = btn.getAttribute('data-emoji');
2227
-
const img = btn.querySelector('img');
2228
-
2229
-
// Display the image in the selected emoji area
2230
-
selectedEmoji.innerHTML = `<img src="${img.src}" alt="${img.alt}" style="width: 100%; height: 100%; object-fit: contain;">`;
2231
-
statusInput.value = emojiValue;
2232
-
closeEmojiPicker();
2233
-
checkForChanges();
2234
-
});
2235
-
});
2236
2260
} else {
2237
2261
// Load standard Unicode emojis
2238
2262
const emojis = emojiData[category] || [];
···
2240
2264
const slug = (window.__emojiSlugs && window.__emojiSlugs[emoji]) || '';
2241
2265
return `<button type="button" class="emoji-option" data-emoji="${emoji}" data-name="${slug}" title="${slug}">${emoji}</button>`;
2242
2266
}).join('');
2243
-
2244
-
// Add click handlers for standard emojis
2245
-
emojiGrid.querySelectorAll('.emoji-option').forEach(btn => {
2246
-
btn.addEventListener('click', (e) => {
2247
-
const emoji = e.target.getAttribute('data-emoji');
2248
-
selectedEmoji.textContent = emoji;
2249
-
statusInput.value = emoji;
2250
-
closeEmojiPicker();
2251
-
checkForChanges();
2252
-
});
2253
-
});
2254
2267
}
2255
2268
2256
2269
// Update active category
···
2259
2272
});
2260
2273
};
2261
2274
2275
+
const applyEmojiSelection = (button) => {
2276
+
if (!button || !selectedEmoji || !statusInput) return;
2277
+
const emojiValue = button.getAttribute('data-emoji');
2278
+
if (!emojiValue) return;
2279
+
2280
+
if (button.classList.contains('custom-emoji')) {
2281
+
const img = button.querySelector('img');
2282
+
if (img) {
2283
+
selectedEmoji.innerHTML = '';
2284
+
const previewImg = document.createElement('img');
2285
+
previewImg.src = img.src;
2286
+
previewImg.alt = img.alt;
2287
+
previewImg.title = img.title || img.alt || '';
2288
+
previewImg.style.width = '100%';
2289
+
previewImg.style.height = '100%';
2290
+
previewImg.style.objectFit = 'contain';
2291
+
selectedEmoji.appendChild(previewImg);
2292
+
}
2293
+
} else {
2294
+
selectedEmoji.textContent = emojiValue;
2295
+
}
2296
+
2297
+
statusInput.value = emojiValue;
2298
+
if (emojiSearch) emojiSearch.value = '';
2299
+
if (emojiCategories) emojiCategories.classList.remove('hidden');
2300
+
closeEmojiPicker();
2301
+
checkForChanges();
2302
+
};
2303
+
2304
+
if (emojiGrid) {
2305
+
emojiGrid.addEventListener('click', (e) => {
2306
+
const button = e.target.closest('.emoji-option');
2307
+
if (!button || !emojiGrid.contains(button)) return;
2308
+
e.preventDefault();
2309
+
applyEmojiSelection(button);
2310
+
});
2311
+
}
2312
+
2262
2313
// Category buttons
2263
2314
document.querySelectorAll('.category-btn').forEach(btn => {
2264
2315
btn.addEventListener('click', () => {
···
2388
2439
// Search emoji function
2389
2440
const searchEmojis = (query) => {
2390
2441
if (!query) {
2391
-
document.getElementById('emoji-categories').classList.remove('hidden');
2442
+
if (emojiCategories) emojiCategories.classList.remove('hidden');
2392
2443
loadEmojiCategory('frequent');
2393
2444
return;
2394
2445
}
2395
2446
2396
2447
// Hide categories when searching
2397
-
document.getElementById('emoji-categories').classList.add('hidden');
2448
+
if (emojiCategories) emojiCategories.classList.add('hidden');
2398
2449
2399
2450
const lowerQuery = query.toLowerCase();
2400
2451
const results = [];
···
2463
2514
} else {
2464
2515
emojiGrid.innerHTML = '<div style="text-align: center; color: var(--text-tertiary); padding: 2rem;">No emojis found</div>';
2465
2516
}
2466
-
2467
-
// Add click handlers for both regular and custom emojis
2468
-
emojiGrid.querySelectorAll('.emoji-option').forEach(btn => {
2469
-
btn.addEventListener('click', (e) => {
2470
-
e.preventDefault();
2471
-
const emojiValue = btn.getAttribute('data-emoji');
2472
-
2473
-
if (btn.classList.contains('custom-emoji')) {
2474
-
// Handle custom emoji
2475
-
const img = btn.querySelector('img');
2476
-
selectedEmoji.innerHTML = `<img src="${img.src}" alt="${img.alt}" style="width: 100%; height: 100%; object-fit: contain;">`;
2477
-
} else {
2478
-
// Handle regular emoji
2479
-
selectedEmoji.textContent = emojiValue;
2480
-
}
2481
-
2482
-
statusInput.value = emojiValue;
2483
-
closeEmojiPicker();
2484
-
checkForChanges();
2485
-
// Clear search when emoji is selected
2486
-
document.getElementById('emoji-search').value = '';
2487
-
});
2488
-
});
2489
2517
};
2490
2518
2491
2519
// Emoji search input handler
2492
-
const emojiSearch = document.getElementById('emoji-search');
2493
2520
if (emojiSearch) {
2494
2521
emojiSearch.addEventListener('input', (e) => {
2495
2522
searchEmojis(e.target.value);