Initial commit - modstagram-next

This commit is contained in:
root
2026-07-05 13:59:41 +03:30
commit 11e73da693
859 changed files with 117640 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
import type { ChatMessage } from "@/components/chat/ChatMessageCard";
export function dedupeChatMessages(messages: ChatMessage[]): ChatMessage[] {
const seen = new Set<string>();
const result: ChatMessage[] = [];
for (const m of messages) {
if (!m) continue;
const idKey =
m._id && /^[a-f\d]{24}$/i.test(m._id)
? m._id
: `${m.senderId}|${m.content}|${m.file}|${m.createdAt}`;
if (seen.has(idKey)) continue;
seen.add(idKey);
result.push(m);
}
return result;
}

View File

@@ -0,0 +1,41 @@
import type { ChatMessage } from "@/components/chat/ChatMessageCard";
const IMAGE_EXT = ["jpg", "jpeg", "png", "webp", "gif", "avif"];
const VIDEO_EXT = ["mp4", "webm", "mov", "m4v"];
const VOICE_EXT = ["mp3", "wav", "aac", "m4a", "ogg", "opus", "webm"];
const DOC_EXT = ["pdf", "zip", "rar", "doc", "docx", "xls", "xlsx", "txt"];
/** Reliable media type — voice never shown as video */
export function detectChatFileType(
fileUrl: string,
declared?: ChatMessage["fileType"]
): ChatMessage["fileType"] | null {
if (declared === "location") return "location";
if (declared === "voice") return "voice";
if (!fileUrl) return declared ?? null;
const lower = fileUrl.toLowerCase();
const ext = lower.split(".").pop()?.split("?")[0] || "";
if (declared === "image" || IMAGE_EXT.includes(ext)) return "image";
if (declared === "voice") return "voice";
if (VOICE_EXT.includes(ext) && ext !== "webm") return "voice";
if (ext === "webm") {
if (
lower.includes("voice") ||
lower.includes("audio") ||
declared === "voice"
) {
return "voice";
}
if (declared === "video" || lower.includes("video")) return "video";
return "voice";
}
if (declared === "video" || VIDEO_EXT.includes(ext)) return "video";
if (declared === "file" || DOC_EXT.includes(ext)) return "file";
if (IMAGE_EXT.includes(ext)) return "image";
if (["mp3", "wav", "aac", "m4a", "opus"].includes(ext)) return "voice";
if (VIDEO_EXT.includes(ext)) return "video";
return "file";
}

View File

@@ -0,0 +1,36 @@
/** Bubble time: hours, minutes, seconds only (no date) */
export function formatBubbleTime(createdAt?: string | null): string {
if (!createdAt) {
return new Date().toLocaleTimeString("fa-IR", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
}
const raw = String(createdAt).trim();
// Jalali datetime "1403-01-15 14:30:45" or ISO
const timePart = raw.includes(" ")
? raw.split(" ").pop()
: raw.length <= 8 && raw.includes(":")
? raw
: null;
if (timePart && /^\d{1,2}:\d{2}(:\d{2})?$/.test(timePart)) {
const parts = timePart.split(":");
if (parts.length === 2) return `${parts[0]}:${parts[1]}:00`;
return timePart;
}
const d = new Date(raw);
if (!Number.isNaN(d.getTime())) {
return d.toLocaleTimeString("fa-IR", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
}
return raw;
}

View File

@@ -0,0 +1,67 @@
import type { ChatMessage } from "@/components/chat/ChatMessageCard";
export type MessageGroup =
| { type: "date"; label: string; key: string }
| { type: "message"; message: ChatMessage; key: string };
function normalizeDateInput(isoOrTime?: string | null): string {
if (isoOrTime == null || String(isoOrTime).trim() === "") {
return new Date().toISOString();
}
return String(isoOrTime);
}
function formatDateLabel(isoOrTime?: string | null): string {
const raw = normalizeDateInput(isoOrTime);
const d = new Date(raw);
if (!Number.isNaN(d.getTime())) {
const today = new Date();
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
if (d.toDateString() === today.toDateString()) return "امروز";
if (d.toDateString() === yesterday.toDateString()) return "دیروز";
return d.toLocaleDateString("fa-IR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
}
const fallback = raw.split(" ")[0];
return fallback || "امروز";
}
function dayKey(isoOrTime?: string | null): string {
const raw = normalizeDateInput(isoOrTime);
const d = new Date(raw);
if (!Number.isNaN(d.getTime())) {
return d.toDateString();
}
return raw.split(" ")[0] || "unknown-day";
}
export function groupMessagesByDate(messages: ChatMessage[]): MessageGroup[] {
const groups: MessageGroup[] = [];
let lastDay = "";
messages.forEach((msg, i) => {
if (!msg) return;
const dk = dayKey(msg.createdAt);
if (dk !== lastDay) {
lastDay = dk;
groups.push({
type: "date",
label: formatDateLabel(msg.createdAt),
key: `date-${dk}`,
});
}
groups.push({
type: "message",
message: msg,
key: msg._id || `m-${i}`,
});
});
return groups;
}