2026-04-25 03:58:26 +08:00
|
|
|
|
// 好友面板事件绑定与列表渲染,替代 Blade 内联脚本。
|
|
|
|
|
|
|
|
|
|
|
|
let friendPanelEventsBound = false;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前房间 ID。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {number|null}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function roomId() {
|
|
|
|
|
|
return window.chatContext?.roomId ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取 CSRF Token。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function csrf() {
|
|
|
|
|
|
return document.querySelector('meta[name="csrf-token"]')?.content ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 在面板顶部显示操作结果提示文字。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} message 提示文字
|
|
|
|
|
|
* @param {string} color 文字颜色
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function setNotice(message, color = "#888") {
|
|
|
|
|
|
const notice = document.getElementById("fp-notice");
|
|
|
|
|
|
if (!notice) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
notice.textContent = message;
|
|
|
|
|
|
notice.style.color = color;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 打开好友面板并自动拉取最新好友数据。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function openFriendPanel() {
|
|
|
|
|
|
const panel = document.getElementById("friend-panel");
|
|
|
|
|
|
if (!panel) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
panel.style.display = "flex";
|
|
|
|
|
|
loadFriends();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭好友面板。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function closeFriendPanel() {
|
|
|
|
|
|
const panel = document.getElementById("friend-panel");
|
|
|
|
|
|
if (panel) {
|
|
|
|
|
|
panel.style.display = "none";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从服务端读取好友列表,接口返回 friends 与 pending 两组数据。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function loadFriends() {
|
|
|
|
|
|
const body = document.getElementById("friend-panel-body");
|
|
|
|
|
|
if (!body) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
body.innerHTML = '<div class="fp-empty">加载中…</div>';
|
|
|
|
|
|
setNotice("");
|
|
|
|
|
|
|
|
|
|
|
|
fetch("/friends", {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
Accept: "application/json",
|
|
|
|
|
|
"X-CSRF-TOKEN": csrf(),
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
|
.then((data) => renderFriends(data.friends || [], data.pending || []))
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
body.innerHTML = '<div class="fp-empty">加载失败,请重试</div>';
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 渲染好友列表与待回加列表。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Array<Record<string, unknown>>} friends 我已添加的好友
|
|
|
|
|
|
* @param {Array<Record<string, unknown>>} pending 对方已加我但我未回加的用户
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function renderFriends(friends, pending) {
|
|
|
|
|
|
const body = document.getElementById("friend-panel-body");
|
|
|
|
|
|
if (!body) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
body.innerHTML = "";
|
|
|
|
|
|
|
|
|
|
|
|
const friendTitle = document.createElement("div");
|
|
|
|
|
|
friendTitle.className = "fp-section-title";
|
|
|
|
|
|
friendTitle.textContent = `📋 我关注的好友(${friends.length})`;
|
|
|
|
|
|
body.appendChild(friendTitle);
|
|
|
|
|
|
|
|
|
|
|
|
if (friends.length === 0) {
|
|
|
|
|
|
appendEmpty(body, "还没有添加任何好友");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
friends.forEach((friend) => body.appendChild(makeFriendRow(friend)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const pendingTitle = document.createElement("div");
|
|
|
|
|
|
pendingTitle.className = "fp-section-title";
|
|
|
|
|
|
pendingTitle.style.marginTop = "10px";
|
|
|
|
|
|
pendingTitle.textContent = `💌 对方已加我,待我回加(${pending.length})`;
|
|
|
|
|
|
body.appendChild(pendingTitle);
|
|
|
|
|
|
|
|
|
|
|
|
if (pending.length === 0) {
|
|
|
|
|
|
appendEmpty(body, "暂无");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
pending.forEach((pendingUser) => body.appendChild(makePendingRow(pendingUser)));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 向容器追加空状态提示。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {HTMLElement} container 父容器
|
|
|
|
|
|
* @param {string} text 提示文字
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function appendEmpty(container, text) {
|
|
|
|
|
|
const empty = document.createElement("div");
|
|
|
|
|
|
empty.className = "fp-empty";
|
|
|
|
|
|
empty.textContent = text;
|
|
|
|
|
|
container.appendChild(empty);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解析好友头像路径。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {unknown} headface 头像字段
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function resolveFriendAvatar(headface) {
|
|
|
|
|
|
const avatar = String(headface || "1.gif");
|
|
|
|
|
|
|
2026-04-25 10:07:36 +08:00
|
|
|
|
// 后端可能返回 storage 相对路径或头像文件名,这里统一转成可直接使用的图片地址。
|
2026-04-25 03:58:26 +08:00
|
|
|
|
return avatar.startsWith("storage/") ? `/${avatar}` : `/images/headface/${avatar}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建我关注的好友行。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Record<string, unknown>} friend 好友数据
|
|
|
|
|
|
* @returns {HTMLElement}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function makeFriendRow(friend) {
|
|
|
|
|
|
const row = document.createElement("div");
|
|
|
|
|
|
row.className = "fp-row";
|
|
|
|
|
|
|
|
|
|
|
|
const username = String(friend.username || "");
|
|
|
|
|
|
const avatar = document.createElement("img");
|
|
|
|
|
|
avatar.className = "fp-avatar";
|
|
|
|
|
|
avatar.src = resolveFriendAvatar(friend.headface);
|
|
|
|
|
|
avatar.alt = username;
|
|
|
|
|
|
|
|
|
|
|
|
const name = document.createElement("span");
|
|
|
|
|
|
name.className = "fp-name";
|
|
|
|
|
|
name.textContent = username;
|
|
|
|
|
|
|
|
|
|
|
|
const status = document.createElement("span");
|
|
|
|
|
|
status.className = `fp-status ${friend.is_online ? "fp-status-online" : "fp-status-offline"}`;
|
|
|
|
|
|
status.textContent = friend.is_online ? "🟢 在线" : "⚫ 离线";
|
|
|
|
|
|
|
|
|
|
|
|
const badge = document.createElement("span");
|
|
|
|
|
|
badge.className = `fp-badge ${friend.mutual ? "fp-badge-mutual" : "fp-badge-onesided"}`;
|
|
|
|
|
|
badge.textContent = friend.mutual ? "💚 互相好友" : "👤 单向关注";
|
|
|
|
|
|
|
|
|
|
|
|
const date = document.createElement("span");
|
|
|
|
|
|
date.className = "fp-date";
|
|
|
|
|
|
date.textContent = String(friend.sub_time || "");
|
|
|
|
|
|
|
|
|
|
|
|
const button = document.createElement("button");
|
|
|
|
|
|
button.className = "fp-action-btn fp-btn-remove";
|
|
|
|
|
|
button.textContent = "删除";
|
2026-04-25 10:07:36 +08:00
|
|
|
|
// 好友行是接口返回后动态创建的,按钮监听直接挂在当前行元素上即可。
|
2026-04-25 03:58:26 +08:00
|
|
|
|
button.addEventListener("click", () => {
|
|
|
|
|
|
void friendAction("remove", username, button);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
row.append(avatar, name, status, badge, date, button);
|
|
|
|
|
|
return row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建待回加用户行。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Record<string, unknown>} pendingUser 待回加用户数据
|
|
|
|
|
|
* @returns {HTMLElement}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function makePendingRow(pendingUser) {
|
|
|
|
|
|
const row = document.createElement("div");
|
|
|
|
|
|
row.className = "fp-row";
|
|
|
|
|
|
|
|
|
|
|
|
const username = String(pendingUser.username || "");
|
|
|
|
|
|
const avatar = document.createElement("img");
|
|
|
|
|
|
avatar.className = "fp-avatar";
|
|
|
|
|
|
avatar.src = resolveFriendAvatar(pendingUser.headface);
|
|
|
|
|
|
avatar.alt = username;
|
|
|
|
|
|
|
|
|
|
|
|
const name = document.createElement("span");
|
|
|
|
|
|
name.className = "fp-name";
|
|
|
|
|
|
name.textContent = username;
|
|
|
|
|
|
|
|
|
|
|
|
const status = document.createElement("span");
|
|
|
|
|
|
status.className = `fp-status ${pendingUser.is_online ? "fp-status-online" : "fp-status-offline"}`;
|
|
|
|
|
|
status.textContent = pendingUser.is_online ? "🟢 在线" : "⚫ 离线";
|
|
|
|
|
|
|
|
|
|
|
|
const date = document.createElement("span");
|
|
|
|
|
|
date.className = "fp-date";
|
|
|
|
|
|
date.textContent = pendingUser.added_at ? `他于 ${pendingUser.added_at} 添加了我` : "";
|
|
|
|
|
|
|
|
|
|
|
|
const button = document.createElement("button");
|
|
|
|
|
|
button.className = "fp-action-btn fp-btn-add";
|
|
|
|
|
|
button.textContent = "➕ 回加";
|
|
|
|
|
|
button.addEventListener("click", () => {
|
|
|
|
|
|
void friendAction("add", username, button);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
row.append(avatar, name, status, date, button);
|
|
|
|
|
|
return row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 添加或删除好友,与双击用户卡片复用同一组后端接口。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {"add"|"remove"} action 操作类型
|
|
|
|
|
|
* @param {string} username 目标用户名
|
|
|
|
|
|
* @param {HTMLButtonElement} button 触发按钮
|
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function friendAction(action, username, button) {
|
|
|
|
|
|
button.disabled = true;
|
|
|
|
|
|
button.style.opacity = "0.5";
|
|
|
|
|
|
setNotice("");
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-04-25 10:10:47 +08:00
|
|
|
|
// 用户名进入 URL path 前必须编码,避免特殊字符破坏路径或请求目标。
|
2026-04-25 03:58:26 +08:00
|
|
|
|
const response = await fetch(`/friend/${encodeURIComponent(username)}/${action}`, {
|
|
|
|
|
|
method: action === "remove" ? "DELETE" : "POST",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"X-CSRF-TOKEN": csrf(),
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
Accept: "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
2026-04-25 10:07:36 +08:00
|
|
|
|
// 房间 ID 用于后端生成房间内通知或校验上下文,不参与前端判权。
|
2026-04-25 03:58:26 +08:00
|
|
|
|
room_id: roomId(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.status === "success") {
|
|
|
|
|
|
setNotice(data.message, "#16a34a");
|
|
|
|
|
|
// 延迟刷新,等待后端好友状态和通知链路落定。
|
|
|
|
|
|
setTimeout(loadFriends, 700);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setNotice(data.message || "操作失败", "#dc2626");
|
|
|
|
|
|
button.disabled = false;
|
|
|
|
|
|
button.style.opacity = "1";
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
setNotice("网络异常,请重试", "#dc2626");
|
|
|
|
|
|
button.disabled = false;
|
|
|
|
|
|
button.style.opacity = "1";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 10:20:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 聊天消息和横幅内的快捷好友操作。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {"add"|"remove"|string} action 操作类型
|
|
|
|
|
|
* @param {string} username 目标用户名
|
|
|
|
|
|
* @param {HTMLElement} element 触发元素
|
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function quickFriendAction(action, username, element) {
|
|
|
|
|
|
if (!["add", "remove"].includes(action) || !username || !(element instanceof HTMLElement)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (element.dataset.done) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
element.dataset.done = "1";
|
|
|
|
|
|
element.textContent = "处理中…";
|
|
|
|
|
|
element.style.pointerEvents = "none";
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 消息内链接来自后端 HTML,用户名进入 path 前仍必须编码。
|
|
|
|
|
|
const response = await fetch(`/friend/${encodeURIComponent(username)}/${action}`, {
|
|
|
|
|
|
method: action === "add" ? "POST" : "DELETE",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
"X-CSRF-TOKEN": csrf(),
|
|
|
|
|
|
Accept: "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
room_id: roomId(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.status === "success") {
|
|
|
|
|
|
element.textContent = action === "add" ? "✅ 已回加" : "✅ 已移除";
|
|
|
|
|
|
element.style.color = "#16a34a";
|
|
|
|
|
|
element.style.textDecoration = "none";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
element.textContent = `❌ ${data.message || "操作失败"}`;
|
|
|
|
|
|
element.style.color = "#cc4444";
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
element.textContent = "❌ 网络错误";
|
|
|
|
|
|
element.style.color = "#cc4444";
|
|
|
|
|
|
delete element.dataset.done;
|
|
|
|
|
|
element.style.pointerEvents = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 03:58:26 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 通过搜索框按用户名添加好友,具体校验仍交给后端。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function friendSearch() {
|
|
|
|
|
|
const input = document.getElementById("friend-search-input");
|
|
|
|
|
|
if (!(input instanceof HTMLInputElement)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const username = input.value.trim();
|
|
|
|
|
|
if (!username) {
|
|
|
|
|
|
setNotice("请输入用户名", "#b45309");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const button = document.getElementById("friend-search-btn");
|
|
|
|
|
|
if (!(button instanceof HTMLButtonElement)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button.disabled = true;
|
|
|
|
|
|
setNotice("正在添加…");
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-04-25 10:10:47 +08:00
|
|
|
|
// 搜索输入的用户名同样先做 path 编码,再交由后端做存在性与权限校验。
|
2026-04-25 03:58:26 +08:00
|
|
|
|
const response = await fetch(`/friend/${encodeURIComponent(username)}/add`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"X-CSRF-TOKEN": csrf(),
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
Accept: "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
2026-04-25 10:07:36 +08:00
|
|
|
|
// 搜索添加也带上当前房间,保持和用户卡片添加好友入口一致。
|
2026-04-25 03:58:26 +08:00
|
|
|
|
room_id: roomId(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.status === "success") {
|
|
|
|
|
|
setNotice(data.message, "#16a34a");
|
|
|
|
|
|
input.value = "";
|
|
|
|
|
|
// 延迟刷新,等待后端好友状态和通知链路落定。
|
|
|
|
|
|
setTimeout(loadFriends, 700);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setNotice(data.message || "添加失败", "#dc2626");
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
setNotice("网络异常", "#dc2626");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
button.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 绑定好友面板关闭、遮罩关闭与搜索入口事件。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function bindFriendPanelControls() {
|
|
|
|
|
|
if (friendPanelEventsBound || typeof document === "undefined") {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
friendPanelEventsBound = true;
|
|
|
|
|
|
document.addEventListener("click", (event) => {
|
|
|
|
|
|
if (!(event.target instanceof Element)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (event.target.closest("[data-friend-panel-close]")) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
closeFriendPanel();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (event.target.closest("[data-friend-search]")) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
void friendSearch();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 10:20:21 +08:00
|
|
|
|
const quickAction = event.target.closest("[data-quick-friend-action]");
|
|
|
|
|
|
if (quickAction) {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
// 后端系统消息只输出 data 属性,具体请求仍统一走模块方法。
|
|
|
|
|
|
void quickFriendAction(
|
|
|
|
|
|
quickAction.getAttribute("data-quick-friend-action") || "",
|
|
|
|
|
|
quickAction.getAttribute("data-quick-friend-username") || "",
|
|
|
|
|
|
quickAction,
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 03:58:26 +08:00
|
|
|
|
const panel = event.target.closest("#friend-panel");
|
|
|
|
|
|
// 只在点击遮罩本身时关闭,避免点击内容区误关。
|
|
|
|
|
|
if (panel && event.target === panel) {
|
|
|
|
|
|
closeFriendPanel();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("keydown", (event) => {
|
|
|
|
|
|
if (event.key !== "Enter" || event.target?.id !== "friend-search-input") {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
void friendSearch();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|