's'
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
const MEDIA_PROXY_PATH = "/api/media/proxy";
|
||||
|
||||
/** 浏览器可直接加载的公开图床,无需经 MinIO 代理缓存 */
|
||||
const DIRECT_MEDIA_HOSTS = [
|
||||
"images.unsplash.com",
|
||||
"unsplash.com",
|
||||
"i.pravatar.cc",
|
||||
"api.dicebear.com",
|
||||
"logo.clearbit.com",
|
||||
"avatars.githubusercontent.com",
|
||||
];
|
||||
|
||||
function configuredMinioPublicUrl(): string {
|
||||
return (
|
||||
process.env.NEXT_PUBLIC_MINIO_PUBLIC_URL ||
|
||||
@@ -8,6 +18,13 @@ function configuredMinioPublicUrl(): string {
|
||||
).replace(/\/$/, "");
|
||||
}
|
||||
|
||||
function isDirectMediaHost(hostname: string): boolean {
|
||||
const host = hostname.toLowerCase();
|
||||
return DIRECT_MEDIA_HOSTS.some(
|
||||
(allowed) => host === allowed || host.endsWith(`.${allowed}`)
|
||||
);
|
||||
}
|
||||
|
||||
export function isExternalMediaUrl(value: string): boolean {
|
||||
return /^https?:\/\//i.test(value);
|
||||
}
|
||||
@@ -22,8 +39,23 @@ export function mediaUrl(value?: string | null): string {
|
||||
if (!url) return "";
|
||||
if (/^(data:|blob:|#|mailto:|tel:)/i.test(url)) return url;
|
||||
if (/^file:/i.test(url)) return "";
|
||||
|
||||
if (url.startsWith("/") && !url.startsWith("//")) {
|
||||
const publicUrl = configuredMinioPublicUrl();
|
||||
return publicUrl ? `${publicUrl}${url}` : url;
|
||||
}
|
||||
|
||||
if (!isExternalMediaUrl(url)) return url;
|
||||
|
||||
if (isMinioUrl(url)) return url;
|
||||
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (isDirectMediaHost(parsed.hostname)) return url;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
|
||||
return `${MEDIA_PROXY_PATH}?src=${encodeURIComponent(url)}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,42 @@ export interface MeetupFeatureOption {
|
||||
en: string;
|
||||
}
|
||||
|
||||
export interface MiroTalkMeetupLike {
|
||||
export interface MeetupLike {
|
||||
id: string;
|
||||
city?: string;
|
||||
date?: string;
|
||||
time?: string;
|
||||
meetingUrl?: string;
|
||||
mirotalkRoom?: string;
|
||||
loungeChannel?: string;
|
||||
mode?: MeetupMode | string;
|
||||
accessLevel?: MeetupAccessLevel | string;
|
||||
}
|
||||
|
||||
export interface MeetupViewer {
|
||||
loaded?: boolean;
|
||||
user?: {
|
||||
id?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
vip?: boolean;
|
||||
role?: string;
|
||||
memberRole?: string;
|
||||
siteRole?: string;
|
||||
} | null;
|
||||
vip?: boolean;
|
||||
}
|
||||
|
||||
export interface MeetupSession {
|
||||
canJoin: boolean;
|
||||
lockReason?: string;
|
||||
displayName: string;
|
||||
videoUrl: string;
|
||||
chatUrl: string;
|
||||
loungeChannel: string;
|
||||
mirotalkRoom: string;
|
||||
meetingProvider: string;
|
||||
user?: MeetupViewer["user"];
|
||||
}
|
||||
|
||||
export const DEFAULT_MIROTALK_URL = "https://mirotalk.nomadro.cn";
|
||||
@@ -41,13 +70,27 @@ export function normalizeMiroTalkRoom(value: string): string {
|
||||
.slice(0, 80);
|
||||
}
|
||||
|
||||
export function createMiroTalkRoomSlug(meetup: MiroTalkMeetupLike): string {
|
||||
export function normalizeLoungeChannel(value: string): string {
|
||||
const channel = value.trim();
|
||||
if (!channel) return "";
|
||||
return channel.startsWith("#") ? channel : `#${channel}`;
|
||||
}
|
||||
|
||||
export function createMiroTalkRoomSlug(meetup: MeetupLike): string {
|
||||
const datePart = String(meetup.date || "").slice(0, 10).replace(/[^0-9]/g, "");
|
||||
const timePart = String(meetup.time || "").slice(0, 5).replace(/[^0-9]/g, "");
|
||||
const cityPart = normalizeMiroTalkRoom(meetup.city || "online");
|
||||
return normalizeMiroTalkRoom(["nomadcna", cityPart, datePart, timePart, meetup.id].filter(Boolean).join("-"));
|
||||
}
|
||||
|
||||
export function createLoungeChannel(meetup: MeetupLike): string {
|
||||
const explicit = normalizeLoungeChannel(meetup.loungeChannel || "");
|
||||
if (explicit) return explicit;
|
||||
const room = normalizeMiroTalkRoom(meetup.mirotalkRoom || "");
|
||||
if (room) return `#${room}`;
|
||||
return `#${createMiroTalkRoomSlug(meetup)}`;
|
||||
}
|
||||
|
||||
export function getMiroTalkBaseUrl(): string {
|
||||
return (
|
||||
process.env.NEXT_PUBLIC_MIROTALK_URL ||
|
||||
@@ -66,7 +109,7 @@ export function getMiroTalkServerLabel(): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildMiroTalkJoinUrl(meetup: MiroTalkMeetupLike, displayName = "NomadCNA"): string {
|
||||
export function buildMiroTalkJoinUrl(meetup: MeetupLike, displayName = "NomadCNA"): string {
|
||||
if (meetup.meetingUrl && /^https?:\/\//i.test(meetup.meetingUrl)) {
|
||||
return meetup.meetingUrl;
|
||||
}
|
||||
@@ -89,8 +132,35 @@ export function getLoungeServerLabel(): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildMeetupChatUrl(): string {
|
||||
return getLoungeBaseUrl();
|
||||
export function buildMeetupChatUrl(meetup: MeetupLike): string {
|
||||
const channel = createLoungeChannel(meetup);
|
||||
return `${getLoungeBaseUrl()}/#/chan-${channel}`;
|
||||
}
|
||||
|
||||
export function buildMeetupLivePath(meetupId: string): string {
|
||||
return `/meetups/${meetupId}/live`;
|
||||
}
|
||||
|
||||
export function viewerDisplayName(viewer: MeetupViewer): string {
|
||||
const name = String(viewer.user?.name || "").trim();
|
||||
if (name) return name;
|
||||
const email = String(viewer.user?.email || "").trim();
|
||||
return email ? email.split("@")[0] : "NomadCNA";
|
||||
}
|
||||
|
||||
export function isHostViewer(viewer: MeetupViewer): boolean {
|
||||
const role = String(
|
||||
viewer.user?.role || viewer.user?.memberRole || viewer.user?.siteRole || ""
|
||||
).toLowerCase();
|
||||
return ["admin", "owner", "host", "organizer", "moderator"].includes(role);
|
||||
}
|
||||
|
||||
export function canAccessMeetup(meetup: Pick<MeetupLike, "accessLevel">, viewer: MeetupViewer): boolean {
|
||||
const access = String(meetup.accessLevel || "public").toLowerCase();
|
||||
if (access === "public") return true;
|
||||
if (access === "members") return Boolean(viewer.user);
|
||||
if (access === "vip") return Boolean(viewer.vip || viewer.user?.vip);
|
||||
return isHostViewer(viewer);
|
||||
}
|
||||
|
||||
export function featureLabels(keys: string[], locale: string): string[] {
|
||||
@@ -99,3 +169,7 @@ export function featureLabels(keys: string[], locale: string): string[] {
|
||||
locale === "zh" ? feature.zh : feature.en
|
||||
);
|
||||
}
|
||||
|
||||
export function isLiveMeetup(meetup: Pick<MeetupLike, "mode">): boolean {
|
||||
return meetup.mode === "online" || meetup.mode === "hybrid";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user