This commit is contained in:
parent
cfef57d8a1
commit
e1dd679c47
19 changed files with 1807 additions and 73 deletions
|
|
@ -183,6 +183,8 @@ const el = {
|
|||
messageList: document.getElementById("message-list"),
|
||||
messageForm: document.getElementById("message-form"),
|
||||
messageBody: document.getElementById("message-body"),
|
||||
mediaUploadBtn: document.getElementById("media-upload-btn"),
|
||||
mediaFileInput: document.getElementById("media-file-input"),
|
||||
|
||||
// User Panel
|
||||
userName: document.getElementById("user-name"),
|
||||
|
|
@ -343,6 +345,43 @@ function formatMessageBody(body) {
|
|||
return escaped;
|
||||
}
|
||||
|
||||
function formatBytes(size) {
|
||||
if (!Number.isFinite(size) || size < 1024) return `${size || 0} B`;
|
||||
const units = ["KB", "MB", "GB"];
|
||||
let value = size;
|
||||
let unitIndex = -1;
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024;
|
||||
unitIndex += 1;
|
||||
}
|
||||
return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
function renderAttachments(attachments = []) {
|
||||
if (!attachments.length) return "";
|
||||
|
||||
return attachments.map((attachment) => {
|
||||
const url = escapeHtml(attachment.media_url);
|
||||
const mime = attachment.mime_type || "application/octet-stream";
|
||||
const fileName = escapeHtml(attachment.original_filename || "file");
|
||||
const size = formatBytes(attachment.size_bytes);
|
||||
|
||||
if (mime.startsWith("image/")) {
|
||||
return `<a class="msg-attachment msg-attachment-image" href="${url}" target="_blank" rel="noopener noreferrer"><img src="${url}" alt="${fileName}" loading="lazy" /></a>`;
|
||||
}
|
||||
|
||||
if (mime.startsWith("video/")) {
|
||||
return `<div class="msg-attachment msg-attachment-video"><video controls preload="metadata" src="${url}"></video></div>`;
|
||||
}
|
||||
|
||||
if (mime.startsWith("audio/")) {
|
||||
return `<div class="msg-attachment msg-attachment-audio"><audio controls preload="metadata" src="${url}"></audio><a href="${url}" target="_blank" rel="noopener noreferrer">${fileName}</a></div>`;
|
||||
}
|
||||
|
||||
return `<a class="msg-attachment msg-attachment-file" href="${url}" target="_blank" rel="noopener noreferrer"><i data-lucide="file"></i><span>${fileName}</span><small>${size}</small></a>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function formatDate(isoString) {
|
||||
const d = new Date(isoString);
|
||||
const now = new Date();
|
||||
|
|
@ -609,9 +648,13 @@ function renderMessages(messages) {
|
|||
row.className = `msg ${isGrouped ? "msg-grouped" : ""}`;
|
||||
|
||||
const displayName = m.author_display_name || "Unknown User";
|
||||
const formattedBody = formatMessageBody(m.body || "");
|
||||
const attachmentsHtml = renderAttachments(m.attachments);
|
||||
const bodyHtml = formattedBody ? `<div class="msg-body">${formattedBody}</div>` : "";
|
||||
const contentHtml = `${bodyHtml}${attachmentsHtml}`;
|
||||
|
||||
if (isGrouped) {
|
||||
row.innerHTML = `<div class="msg-content"><div class="msg-body">${formatMessageBody(m.body)}</div></div>`;
|
||||
row.innerHTML = `<div class="msg-content">${contentHtml}</div>`;
|
||||
} else {
|
||||
row.innerHTML = `
|
||||
<div class="msg-avatar">${shortName(displayName)}</div>
|
||||
|
|
@ -620,7 +663,7 @@ function renderMessages(messages) {
|
|||
<span class="msg-author">${escapeHtml(displayName)}</span>
|
||||
<span class="msg-time">${formatDate(m.created_at)}</span>
|
||||
</div>
|
||||
<div class="msg-body">${formatMessageBody(m.body)}</div>
|
||||
${contentHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -644,6 +687,15 @@ function renderMessages(messages) {
|
|||
gifImage.addEventListener("load", scrollMessagesToBottom, { once: true });
|
||||
gifImage.addEventListener("error", scrollMessagesToBottom, { once: true });
|
||||
}
|
||||
|
||||
for (const media of el.messageList.querySelectorAll(".msg-attachment img, .msg-attachment video, .msg-attachment audio")) {
|
||||
if ("complete" in media && media.complete) continue;
|
||||
media.addEventListener("loadeddata", scrollMessagesToBottom, { once: true });
|
||||
media.addEventListener("load", scrollMessagesToBottom, { once: true });
|
||||
media.addEventListener("error", scrollMessagesToBottom, { once: true });
|
||||
}
|
||||
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function renderMembers() {
|
||||
|
|
@ -1862,6 +1914,26 @@ async function init() {
|
|||
} catch (err) { alert(err.message); }
|
||||
};
|
||||
|
||||
el.mediaUploadBtn.onclick = () => {
|
||||
if (!state.selectedTextChannelId && !state.selectedDmUserId) {
|
||||
alert("Select a chat first.");
|
||||
return;
|
||||
}
|
||||
el.mediaFileInput.click();
|
||||
};
|
||||
|
||||
el.mediaFileInput.onchange = async () => {
|
||||
const file = el.mediaFileInput.files?.[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
await uploadMediaFile(file);
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
} finally {
|
||||
el.mediaFileInput.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
// 4. Voice/Soundboard Controls
|
||||
el.voiceVideoBtn.onclick = toggleVideo;
|
||||
el.voiceScreenBtn.onclick = toggleScreenShare;
|
||||
|
|
@ -1962,6 +2034,54 @@ async function init() {
|
|||
}
|
||||
}
|
||||
|
||||
async function uploadMediaFile(file) {
|
||||
const token = storageGet("chattz_token");
|
||||
if (!token) throw new Error("You need to log in again.");
|
||||
|
||||
const path = state.selectedTextChannelId
|
||||
? `/channels/${state.selectedTextChannelId}/attachments`
|
||||
: state.selectedDmUserId
|
||||
? `/dms/${state.selectedDmUserId}/attachments`
|
||||
: null;
|
||||
if (!path) throw new Error("Select a chat first.");
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
el.mediaUploadBtn.disabled = true;
|
||||
el.mediaUploadBtn.title = "Uploading...";
|
||||
|
||||
const fullUrl = API_BASE_URL ? `${API_BASE_URL}${path}` : path;
|
||||
try {
|
||||
const response = await fetch(fullUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let detail = "upload failed";
|
||||
try {
|
||||
const payload = await response.json();
|
||||
detail = payload.error || detail;
|
||||
} catch { }
|
||||
throw new Error(`${response.status}: ${detail}`);
|
||||
}
|
||||
|
||||
const messages = state.selectedTextChannelId
|
||||
? await api(`/channels/${state.selectedTextChannelId}/messages?limit=100`)
|
||||
: await api(`/dms/${state.selectedDmUserId}/messages?limit=100`);
|
||||
renderMessages(messages);
|
||||
await loadDMConversations();
|
||||
renderDMs();
|
||||
} finally {
|
||||
el.mediaUploadBtn.disabled = false;
|
||||
el.mediaUploadBtn.title = "Upload File";
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Initial Data Loading & App Start
|
||||
try {
|
||||
state.me = await api("/me");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue