138 lines
4.8 KiB
JavaScript
138 lines
4.8 KiB
JavaScript
const viewerState = {
|
|
channels: [],
|
|
channelId: null,
|
|
oldestMessageId: null,
|
|
};
|
|
|
|
async function api(path) {
|
|
const response = await fetch(path);
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(data.error || "Request failed");
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function textNodeWithBreaks(text) {
|
|
const fragment = document.createDocumentFragment();
|
|
const parts = (text || "").split("\n");
|
|
parts.forEach((part, index) => {
|
|
if (index > 0) fragment.appendChild(document.createElement("br"));
|
|
fragment.appendChild(document.createTextNode(part));
|
|
});
|
|
return fragment;
|
|
}
|
|
|
|
function renderChannelList() {
|
|
const root = document.getElementById("viewer-channel-list");
|
|
root.innerHTML = "";
|
|
const template = document.getElementById("viewer-channel-template");
|
|
viewerState.channels.forEach((channel) => {
|
|
const node = template.content.firstElementChild.cloneNode(true);
|
|
node.querySelector(".viewer-channel-name").textContent = channel.name;
|
|
node.querySelector(".viewer-channel-meta").textContent = `${channel.message_count} сообщений`;
|
|
if (channel.channel_id === viewerState.channelId) {
|
|
node.classList.add("active");
|
|
}
|
|
node.addEventListener("click", () => {
|
|
viewerState.oldestMessageId = null;
|
|
loadChannel(channel.channel_id);
|
|
});
|
|
root.appendChild(node);
|
|
});
|
|
}
|
|
|
|
function renderMessages(payload, append = false) {
|
|
const root = document.getElementById("messages-list");
|
|
const template = document.getElementById("message-template");
|
|
if (!append) root.innerHTML = "";
|
|
|
|
const channel = payload.channel;
|
|
document.getElementById("viewer-title").textContent = channel?.name || payload.channel_id;
|
|
document.getElementById("viewer-subtitle").textContent = channel
|
|
? `${channel.message_count} сообщений, последнее: ${channel.last_date || "—"}`
|
|
: "База данных для канала пока не найдена.";
|
|
|
|
payload.messages.forEach((message) => {
|
|
const node = template.content.firstElementChild.cloneNode(true);
|
|
node.dataset.messageId = String(message.message_id);
|
|
node.querySelector(".message-author").textContent = message.sender_name || "Unknown";
|
|
node.querySelector(".message-date").textContent = message.date || "";
|
|
|
|
const reply = node.querySelector(".message-reply");
|
|
if (message.reply_to) {
|
|
reply.textContent = `Ответ на сообщение #${message.reply_to}`;
|
|
}
|
|
|
|
node.querySelector(".message-text").appendChild(textNodeWithBreaks(message.text || ""));
|
|
|
|
const media = node.querySelector(".message-media");
|
|
if (message.media_url && message.media_kind === "image") {
|
|
const img = document.createElement("img");
|
|
img.src = message.media_url;
|
|
img.loading = "lazy";
|
|
media.appendChild(img);
|
|
} else if (message.media_url && message.media_kind === "video") {
|
|
const video = document.createElement("video");
|
|
video.src = message.media_url;
|
|
video.controls = true;
|
|
video.preload = "metadata";
|
|
media.appendChild(video);
|
|
} else if (message.media_url) {
|
|
const link = document.createElement("a");
|
|
link.href = message.media_url;
|
|
link.target = "_blank";
|
|
link.rel = "noreferrer";
|
|
link.textContent = "Открыть файл";
|
|
media.appendChild(link);
|
|
}
|
|
|
|
const footerParts = [];
|
|
if (message.views) footerParts.push(`views: ${message.views}`);
|
|
if (message.forwards) footerParts.push(`forwards: ${message.forwards}`);
|
|
if (message.reactions) footerParts.push(`reactions: ${message.reactions}`);
|
|
node.querySelector(".message-footer").textContent = footerParts.join(" | ");
|
|
|
|
if (append) {
|
|
root.prepend(node);
|
|
} else {
|
|
root.appendChild(node);
|
|
}
|
|
});
|
|
|
|
if (payload.messages.length) {
|
|
viewerState.oldestMessageId = payload.messages[0].message_id;
|
|
}
|
|
|
|
document.getElementById("load-older-btn").disabled = !payload.messages.length;
|
|
}
|
|
|
|
async function loadChannel(channelId, append = false) {
|
|
viewerState.channelId = channelId;
|
|
renderChannelList();
|
|
const before = append && viewerState.oldestMessageId ? `&before=${viewerState.oldestMessageId}` : "";
|
|
const payload = await api(`/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`);
|
|
renderMessages(payload, append);
|
|
}
|
|
|
|
async function main() {
|
|
viewerState.channels = await api("/api/channels");
|
|
const params = new URLSearchParams(window.location.search);
|
|
viewerState.channelId = params.get("channel") || viewerState.channels[0]?.channel_id || null;
|
|
renderChannelList();
|
|
|
|
if (viewerState.channelId) {
|
|
await loadChannel(viewerState.channelId);
|
|
}
|
|
|
|
document.getElementById("load-older-btn").addEventListener("click", async () => {
|
|
if (!viewerState.channelId || !viewerState.oldestMessageId) return;
|
|
await loadChannel(viewerState.channelId, true);
|
|
});
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
alert(error.message);
|
|
});
|