{"version":3,"file":"userprofile-DEmXRdtf.mjs","sources":["../src/components/navtabs.tsx","../src/pages/userprofile/userinfo.tsx","../src/components/FormatTime.tsx","../src/pages/userprofile/usercomments.tsx","../src/pages/userprofile/userlikes.tsx","../src/libs/teacherCourses.ts","../src/pages/userprofile/userprofile.tsx","../src/pages/userprofile/index.tsx"],"sourcesContent":["import React from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from '../libs/i18n';\nimport './navtabs.scss';\n\nexport interface NavTabItem {\n    key: string;\n    label: string;\n    disabled?: boolean;\n    href?: string;\n}\n\ninterface BaseNavTabsProps {\n    items: NavTabItem[];\n    activeKey: string;\n    onTabChange: (key: string) => void;\n    className?: string;\n}\n\ninterface DefaultNavTabsProps extends BaseNavTabsProps {\n    variant?: 'default';\n}\n\ninterface ScrollableNavTabsProps extends BaseNavTabsProps {\n    variant: 'scrollable';\n    showLeftArrow?: boolean;\n    showRightArrow?: boolean;\n    onScrollArrowClick?: (direction: 'left' | 'right') => void;\n    scrollContainerRef?: React.RefObject<HTMLDivElement | null>;\n}\n\nexport type NavTabsProps = DefaultNavTabsProps | ScrollableNavTabsProps;\n\nexport function NavTabs(props: NavTabsProps) {\n    const { t } = useTranslation();\n    const { items, activeKey, onTabChange, className } = props;\n\n    const variant = props.variant || 'default';\n    const showLeftArrow =\n        props.variant === 'scrollable' ? props.showLeftArrow || false : false;\n    const showRightArrow =\n        props.variant === 'scrollable' ? props.showRightArrow || false : false;\n    const onScrollArrowClick =\n        props.variant === 'scrollable' ? props.onScrollArrowClick : undefined;\n    const scrollContainerRef =\n        props.variant === 'scrollable' ? props.scrollContainerRef : undefined;\n    const handleTabClick = (key: string) => {\n        if (onTabChange) {\n            onTabChange(key);\n        }\n    };\n\n    if (variant === 'scrollable') {\n        return (\n            <div\n                className={classNames('nav-tabs-scrollable', className, {\n                    'show-left-gradient': showLeftArrow,\n                    'show-right-gradient': showRightArrow,\n                })}\n            >\n                {/* Left scroll arrow */}\n                {showLeftArrow && onScrollArrowClick && (\n                    <button\n                        className=\"nav-tabs-scroll-arrow nav-tabs-scroll-arrow-left\"\n                        onClick={() => onScrollArrowClick('left')}\n                        aria-label={t('ui.common.scrollLeft')}\n                    >\n                        <i className=\"bi bi-chevron-left\"></i>\n                    </button>\n                )}\n\n                {/* Right scroll arrow */}\n                {showRightArrow && onScrollArrowClick && (\n                    <button\n                        className=\"nav-tabs-scroll-arrow nav-tabs-scroll-arrow-right\"\n                        onClick={() => onScrollArrowClick('right')}\n                        aria-label={t('ui.common.scrollRight')}\n                    >\n                        <i className=\"bi bi-chevron-right\"></i>\n                    </button>\n                )}\n\n                {/* Scrollable tabs wrapper */}\n                <div className=\"nav-tabs-wrapper\">\n                    <div\n                        className=\"nav-tabs-scrollable-inner\"\n                        ref={scrollContainerRef}\n                        role=\"tablist\"\n                    >\n                        {items.map(item => (\n                            <div className=\"nav-item\" key={item.key}>\n                                <button\n                                    type=\"button\"\n                                    className={classNames({\n                                        'nav-link': true,\n                                        active: activeKey === item.key,\n                                    })}\n                                    onClick={() =>\n                                        !item.disabled &&\n                                        handleTabClick(item.key)\n                                    }\n                                    disabled={item.disabled}\n                                    role=\"tab\"\n                                    aria-selected={activeKey === item.key}\n                                >\n                                    {item.label}\n                                </button>\n                            </div>\n                        ))}\n                    </div>\n                </div>\n            </div>\n        );\n    }\n\n    // Default variant - simple nav tabs\n    return (\n        <div\n            className={classNames('nav-tabs-default', className)}\n            role=\"tablist\"\n        >\n            {items.map(item => (\n                <div className=\"nav-item\" key={item.key}>\n                    <button\n                        type=\"button\"\n                        className={classNames('px-0', {\n                            'nav-link': true,\n                            active: activeKey === item.key,\n                        })}\n                        onClick={() =>\n                            !item.disabled && handleTabClick(item.key)\n                        }\n                        disabled={item.disabled}\n                        role=\"tab\"\n                        aria-selected={activeKey === item.key}\n                    >\n                        {item.label}\n                    </button>\n                </div>\n            ))}\n        </div>\n    );\n}\n\nexport default NavTabs;\n","import { FC } from 'react';\n\nimport { useAuth } from '../../libs';\nimport { useTranslation } from '../../libs/i18n';\nimport { PublicUserProfile } from '@zentube/schema';\nimport { FollowButton, VerifiedBadge, UserAvatar } from '../../components';\n\ninterface UserInfoProps {\n    profile: PublicUserProfile;\n}\nconst userinfo: FC<UserInfoProps> = ({ profile }) => {\n    const { t } = useTranslation();\n    const { user: currentUser } = useAuth();\n\n    return (\n        <div className=\"user-header-section mb-5\">\n            <div className=\"d-flex align-items-start gap-4\">\n                {/* 用户头像 */}\n                <div className=\"user-avatar-container\">\n                    <UserAvatar\n                        username={profile.username}\n                        displayName={profile.displayName}\n                        avatarUrls={profile.avatarUrls}\n                        size=\"large\"\n                        className=\"user-avatar\"\n                    />\n                </div>\n\n                {/* 用户信息 */}\n                <div className=\"user-info-container flex-grow-1\">\n                    {/* 用户名和验证徽章 */}\n                    <div className=\"d-flex align-items-center mb-2\">\n                        <h4 className=\"user-display-name mb-0\">\n                            {profile.displayName}\n                        </h4>\n                        <VerifiedBadge isVerified={profile.isVerified} />\n                    </div>\n\n                    {/* @用户名 */}\n                    <div className=\"text-secondary mb-3\">\n                        @{profile.username}\n                    </div>\n\n                    {/* 统计数据 */}\n                    <div className=\"d-flex mb-3\">\n                        <div>\n                            <span className=\"text-secondary\">\n                                {t(\n                                    'ui.user.videos',\n                                    {\n                                        count: profile.videosCount,\n                                    },\n                                    {\n                                        count: profile.videosCount,\n                                    }\n                                )}\n                            </span>\n                        </div>\n                        {profile.followersCount !== undefined && (\n                            <div className=\"mx-3\">\n                                <span className=\"text-secondary\">\n                                    {t(\n                                        'ui.user.followers',\n                                        {\n                                            count: profile.followersCount,\n                                        },\n                                        {\n                                            count: profile.followersCount,\n                                        }\n                                    )}\n                                </span>\n                            </div>\n                        )}\n                    </div>\n\n                    {/* 个人简介 */}\n                    {profile.bio && <div className=\"mb-3\">{profile.bio}</div>}\n\n                    {/* Follow按钮 - 对所有用户显示（除了用户本人）*/}\n                    {Number(currentUser?.id || 0) !== Number(profile.id) && (\n                        <div className=\"user-actions\">\n                            <FollowButton userId={profile.id} />\n                        </div>\n                    )}\n                </div>\n            </div>\n        </div>\n    );\n};\n\nexport default userinfo;\n","import { FC, memo } from 'react';\nimport { useTranslation } from '../libs/i18n';\n\nimport classNames from 'classnames';\nimport dayjs from 'dayjs';\nimport timezone from 'dayjs/plugin/timezone';\nimport utc from 'dayjs/plugin/utc';\n\ninterface Props {\n    time: string;\n    className?: string;\n    preFix?: string;\n}\n\ndayjs.extend(timezone);\ndayjs.extend(utc);\n\nconst Index: FC<Props> = ({ time, preFix, className }) => {\n    const { t } = useTranslation();\n    const formatTime = (from: number) => {\n        const now = Math.floor(dayjs().valueOf() / 1000);\n        const between = now > from ? now - from : 0;\n\n        if (between <= 1) {\n            return t('ui.dates.now');\n        }\n        if (between > 1 && between < 60) {\n            return t('ui.dates.x_seconds_ago', { count: between });\n        }\n\n        if (between >= 60 && between < 3600) {\n            const min = Math.floor(between / 60);\n            return t('ui.dates.x_minutes_ago', { count: min });\n        }\n        if (between >= 3600 && between < 3600 * 24) {\n            const h = Math.floor(between / 3600);\n            return t('ui.dates.x_hours_ago', { count: h });\n        }\n\n        if (\n            between >= 3600 * 24 &&\n            between < 3600 * 24 * 366 &&\n            dayjs.unix(from).format('YYYY') === dayjs.unix(now).format('YYYY')\n        ) {\n            return dayjs.unix(from).tz().format(t('ui.dates.long_date'));\n        }\n\n        return dayjs.unix(from).tz().format(t('ui.dates.long_date_with_year'));\n    };\n\n    if (!time) {\n        return null;\n    }\n\n    return (\n        <time\n            className={classNames('', className)}\n            dateTime={dayjs(time).tz().toISOString()}\n            title={dayjs(time).tz().format(t('ui.dates.long_date_with_time'))}\n        >\n            {preFix ? `${preFix} ` : ''}\n            {formatTime(dayjs(time).tz().unix())}\n        </time>\n    );\n};\n\nexport default memo(Index);\n","import { useEffect, useState } from 'react';\nimport { Alert } from 'react-bootstrap';\nimport { UserCommentItem, UserCommentsListResponse } from '@zentube/schema';\nimport { api, getErrorMessage } from '../../libs';\nimport { useTranslation } from '../../libs/i18n';\nimport { appendCurrentChannel } from '../../libs/url';\nimport FormatTime from '../../components/FormatTime';\n\ninterface UserCommentsProps {\n    userId: number;\n    shouldLoad?: boolean;\n}\n\nexport function UserComments({\n    userId,\n    shouldLoad = false,\n}: UserCommentsProps) {\n    const { t } = useTranslation();\n    const [comments, setComments] = useState<UserCommentItem[]>([]);\n    const [loading, setLoading] = useState(false);\n    const [error, setError] = useState('');\n\n    const loadUserComments = async () => {\n        if (loading) return;\n\n        setLoading(true);\n        setError('');\n\n        try {\n            const queryParams = new URLSearchParams({\n                page: '1',\n                pageSize: '30',\n            });\n\n            const response = await api.get<UserCommentsListResponse>(\n                `/api/users/${userId}/comments?${queryParams}`\n            );\n\n            const data = response.data.data;\n            setComments(data.comments);\n        } catch (err) {\n            setError(getErrorMessage(err));\n        } finally {\n            setLoading(false);\n        }\n    };\n\n    useEffect(() => {\n        if (shouldLoad && comments.length === 0) {\n            loadUserComments();\n        }\n    }, [userId, shouldLoad]);\n\n    if (loading && comments.length === 0) {\n        return (\n            <div className=\"text-center py-5\">\n                <div className=\"spinner-border text-primary\" role=\"status\">\n                    <span className=\"visually-hidden\">\n                        {t('common.loading')}\n                    </span>\n                </div>\n            </div>\n        );\n    }\n\n    if (error) {\n        return <Alert variant=\"danger\">{error}</Alert>;\n    }\n\n    if (comments.length === 0 && !loading) {\n        return (\n            <div className=\"text-center py-5 text-muted\">\n                <i className=\"bi bi-chat-dots fs-1\"></i>\n                <p className=\"mt-3\">\n                    {t('ui.empty.noComments', {\n                        defaultValue: 'No comments yet',\n                    })}\n                </p>\n                <p>\n                    {t('ui.empty.noCommentsDesc', {\n                        defaultValue: \"This user hasn't made any comments.\",\n                    })}\n                </p>\n            </div>\n        );\n    }\n\n    return (\n        <>\n            {comments.map(comment => (\n                <div key={comment.id} className=\"mb-4\">\n                    <a\n                        href={appendCurrentChannel(\n                            `/video/${comment.video.id}`\n                        )}\n                        className=\"text-decoration-none\"\n                    >\n                        {comment.video.title}\n                    </a>\n                    <div className=\"text-small mb-2\">{comment.content}</div>\n                    <div className=\"text-muted text-small\">\n                        <FormatTime time={comment.createdAt} />\n                    </div>\n                </div>\n            ))}\n        </>\n    );\n}\n","import { useEffect, useState } from 'react';\nimport { Alert, Col, Row } from 'react-bootstrap';\nimport {\n    getMasterProfile,\n    UserLikeRecord,\n    UserLikesListResponse,\n} from '@zentube/schema';\nimport { api, getErrorMessage } from '../../libs';\nimport { useTranslation } from '../../libs/i18n';\nimport { appendCurrentChannel } from '../../libs/url';\nimport FormatTime from '../../components/FormatTime';\ninterface UserLikesProps {\n    userId: number;\n    shouldLoad?: boolean;\n}\n\nexport function UserLikes({ userId, shouldLoad = false }: UserLikesProps) {\n    const { t } = useTranslation();\n    const masterSlug = new URLSearchParams(window.location.search).get(\n        'master'\n    );\n    const master = masterSlug ? getMasterProfile(masterSlug) : undefined;\n    const [likes, setLikes] = useState<UserLikeRecord[]>([]);\n    const [loading, setLoading] = useState(false);\n    const [error, setError] = useState('');\n\n    const loadUserLikes = async () => {\n        if (loading) return;\n\n        setLoading(true);\n        setError('');\n\n        try {\n            const queryParams = new URLSearchParams({\n                page: '1',\n                pageSize: '30',\n            });\n            if (master) queryParams.set('master', master.slug);\n\n            const response = await api.get<UserLikesListResponse>(\n                `/api/users/${userId}/likes?${queryParams}`\n            );\n\n            const data = response.data.data;\n            setLikes(data.likes);\n        } catch (err) {\n            setError(getErrorMessage(err));\n        } finally {\n            setLoading(false);\n        }\n    };\n\n    useEffect(() => {\n        if (shouldLoad && likes.length === 0) {\n            loadUserLikes();\n        }\n    }, [userId, shouldLoad]);\n\n    if (loading && likes.length === 0) {\n        return (\n            <div className=\"text-center py-5\">\n                <div className=\"spinner-border text-primary\" role=\"status\">\n                    <span className=\"visually-hidden\">\n                        {t('common.loading')}\n                    </span>\n                </div>\n            </div>\n        );\n    }\n\n    if (error) {\n        return <Alert variant=\"danger\">{error}</Alert>;\n    }\n\n    if (likes.length === 0 && !loading) {\n        return (\n            <div className=\"text-center py-5 text-muted\">\n                <i className=\"bi bi-heart fs-1\"></i>\n                <p className=\"mt-3\">\n                    {t('ui.empty.noLikes', { defaultValue: 'No likes yet' })}\n                </p>\n                <p>\n                    {t('ui.empty.noLikesDesc', {\n                        defaultValue: \"This user hasn't liked any videos.\",\n                    })}\n                </p>\n            </div>\n        );\n    }\n\n    return (\n        <>\n            {master && (\n                <p className=\"text-muted\">\n                    {master.copy['zh-CN'].kicker} · {t('ui.nav.likedVideos')}\n                </p>\n            )}\n            <Row className=\"g-3\">\n                {likes\n                    .filter(item => item.video)\n                    .map((like, index) => (\n                        <Col key={`${like.video.id}-${index}`} xs={12}>\n                            <div>\n                                <a\n                                    href={appendCurrentChannel(\n                                        `/video/${like.video?.id}`\n                                    )}\n                                    className=\"text-decoration-none\"\n                                >\n                                    {like.video?.title}\n                                </a>\n                                <div className=\"text-muted text-small\">\n                                    <FormatTime time={like.createdAt} />\n                                </div>\n                            </div>\n                        </Col>\n                    ))}\n            </Row>\n        </>\n    );\n}\n","import type { Video } from '@zentube/schema';\n\nexport type TeacherCourse = {\n    title: string;\n    summary: string;\n    videos: Video[];\n};\n\nexport type TeacherCourseGroups = {\n    courses: TeacherCourse[];\n    latestVideos: Video[];\n};\n\nconst lessonNumber = (title: string) =>\n    Number(title.match(/第\\s*(\\d+)\\s*讲/)?.[1]) || 0;\n\nconst courseTitle = (title: string) =>\n    title\n        .match(/^(.+?)第\\s*\\d+\\s*讲/)?.[1]\n        ?.replace(/[\\s:：｜|·、,，\\-—–_]+$/u, '')\n        .trim();\n\nexport function groupTeacherCourseVideos(videos: Video[]): TeacherCourseGroups {\n    const groups = new Map<string, Video[]>();\n    const latestVideos: Video[] = [];\n\n    for (const video of videos) {\n        const title = courseTitle(video.title);\n        if (!title) {\n            latestVideos.push(video);\n            continue;\n        }\n        groups.set(title, [...(groups.get(title) || []), video]);\n    }\n\n    // ponytail: title grouping is a fallback; replace with editorial course relations once they exist.\n    return {\n        courses: [...groups].map(([title, lessons]) => ({\n            title,\n            summary: `已收录 ${lessons.length} 讲`,\n            videos: lessons.sort(\n                (left, right) =>\n                    lessonNumber(left.title) - lessonNumber(right.title)\n            ),\n        })),\n        latestVideos,\n    };\n}\n","import { useState, useEffect, useRef, useCallback } from 'react';\nimport { Col, Row, Tab } from 'react-bootstrap';\n\nimport UserInfo from './userinfo';\nimport {\n    FollowButton,\n    InfiniteScrollVideoGrid,\n    NavTabs,\n    UserAvatar,\n    VerifiedBadge,\n} from '../../components';\nimport { UserComments } from './usercomments';\nimport { UserLikes } from './userlikes';\n\nimport { PublicUserProfile, Video } from '@zentube/schema';\nimport { api, getErrorMessage, useAuth } from '../../libs';\nimport { useTranslation } from '../../libs/i18n';\nimport { appPath, appendCurrentChannel } from '../../libs/url';\nimport { teacherProfiles } from '../../libs/teacherProfiles';\nimport { groupTeacherCourseVideos } from '../../libs/teacherCourses';\nimport { useInfiniteScroll } from '../../libs/useInfiniteScroll';\nimport './styles.scss';\n\nexport interface UserProfileProps {\n    username: string;\n    initialTab?: string;\n}\n\nexport function UserProfile({ username, initialTab }: UserProfileProps) {\n    const { t } = useTranslation();\n    const { user: currentUser } = useAuth();\n    const [profile, setProfile] = useState<PublicUserProfile | null>(null);\n    const [videos, setVideos] = useState<Video[]>([]);\n    const [loading, setLoading] = useState(true);\n    const [videosLoading, setVideosLoading] = useState(false);\n    const [error, setError] = useState('');\n    const [currentPage, setCurrentPage] = useState(1);\n    const [hasMore, setHasMore] = useState(false);\n    const [activeTab, setActiveTab] = useState(\n        initialTab === 'votes' ? 'likes' : initialTab || 'videos'\n    );\n    const [expandedCourse, setExpandedCourse] = useState<string | null>(null);\n    const videosInitialized = useRef(false);\n\n    useEffect(() => {\n        setVideos([]);\n        setCurrentPage(1);\n        setHasMore(false);\n        setExpandedCourse(null);\n        videosInitialized.current = false;\n        // 页面初始化时加载用户信息\n        loadUserProfile();\n\n        // 如果初始tab是videos，则加载视频\n        if (\n            activeTab === 'videos' ||\n            teacherProfiles.some(teacher => teacher.username === username)\n        ) {\n            loadUserVideos(1);\n            videosInitialized.current = true;\n        }\n    }, [username]);\n\n    useEffect(() => {\n        // 只在切换到videos tab且还没有初始化过视频时加载视频\n        if (activeTab === 'videos' && !videosInitialized.current) {\n            loadUserVideos(1);\n            videosInitialized.current = true;\n        }\n        // comments和likes的数据由各自的组件负责加载\n    }, [activeTab]);\n\n    const loadUserProfile = async () => {\n        try {\n            setLoading(true);\n            setError('');\n            const response = await api.get(`/api/users/${username}/profile`);\n\n            if (response.data.success) {\n                setProfile(response.data.data);\n            }\n        } catch (err) {\n            setError(getErrorMessage(err));\n        } finally {\n            setLoading(false);\n        }\n    };\n\n    const loadUserVideos = useCallback(\n        async (page: number = 1) => {\n            try {\n                setVideosLoading(true);\n                const response = await api.get(\n                    `/api/users/${username}/videos`,\n                    {\n                        params: { page, pageSize: 24 },\n                    }\n                );\n\n                if (response.data.success) {\n                    const newVideos = response.data.data.videos;\n                    const pagination = response.data.data.pagination;\n\n                    setVideos(previousVideos =>\n                        page === 1\n                            ? newVideos\n                            : [...previousVideos, ...newVideos]\n                    );\n                    setCurrentPage(pagination.page);\n\n                    // More reliable hasMore calculation\n                    setHasMore(pagination.page < pagination.totalPages);\n                }\n            } catch (err) {\n                setError(getErrorMessage(err));\n            } finally {\n                setVideosLoading(false);\n            }\n        },\n        [username]\n    );\n\n    const handleLoadMore = useCallback(() => {\n        if (!videosLoading && hasMore) {\n            loadUserVideos(currentPage + 1);\n        }\n    }, [videosLoading, hasMore, currentPage, loadUserVideos]);\n    const teacherSentinelRef = useInfiniteScroll({\n        hasMore,\n        loading: videosLoading,\n        onLoadMore: handleLoadMore,\n        threshold: 200,\n    });\n\n    const handleVideoClick = (video: Video) => {\n        if (video.status === 'draft') {\n            window.location.href = appPath(`/video/draft?id=${video.id}`);\n        } else {\n            window.location.href = appendCurrentChannel(`/video/${video.id}`);\n        }\n    };\n\n    const handleTabChange = (tabKey: string | null) => {\n        if (!tabKey) return;\n\n        setActiveTab(tabKey);\n\n        // Update URL with tab parameter\n        const url = new URL(window.location.href);\n        if (tabKey === 'videos') {\n            url.searchParams.delete('tab');\n        } else {\n            url.searchParams.set('tab', tabKey);\n        }\n        window.history.pushState({}, '', url.toString());\n    };\n\n    if (loading) {\n        return (\n            <div\n                className=\"d-flex justify-content-center align-items-center\"\n                style={{ height: '50vh' }}\n            >\n                <div className=\"spinner-border\" role=\"status\">\n                    <span className=\"visually-hidden\">Loading...</span>\n                </div>\n            </div>\n        );\n    }\n\n    if (error) {\n        return (\n            <div className=\"container mt-4\">\n                <div className=\"alert alert-danger\" role=\"alert\">\n                    <h4 className=\"alert-heading\">Error</h4>\n                    <p>{error}</p>\n                </div>\n            </div>\n        );\n    }\n\n    if (!profile) {\n        return (\n            <div className=\"container mt-4\">\n                <div className=\"alert alert-warning\" role=\"alert\">\n                    User not found\n                </div>\n            </div>\n        );\n    }\n\n    // 检查是否有权限查看评论和投票记录（仅自己和管理员可见）\n    const canViewPrivateTabs =\n        currentUser &&\n        (currentUser.id === profile.id || currentUser.role === 'admin');\n    const teacherProfile = teacherProfiles.find(\n        teacher => teacher.username === profile.username\n    );\n    const isTeacher = Boolean(teacherProfile);\n\n    if (isTeacher) {\n        const { courses, latestVideos } = groupTeacherCourseVideos(videos);\n        const publicCount = profile.videosCount || videos.length;\n        const teacherBio = profile.bio?.replace(/\\\\n/g, '\\n');\n\n        return (\n            <div className=\"teacher-profile-page\">\n                <aside className=\"teacher-profile-aside\">\n                    <UserAvatar\n                        username={profile.username}\n                        displayName={teacherProfile?.name}\n                        avatarUrls={profile.avatarUrls}\n                        preferTeacherPortrait\n                        size={220}\n                        className=\"teacher-profile-portrait\"\n                    />\n                    <div className=\"teacher-profile-name\">\n                        <h1>{teacherProfile?.name}</h1>\n                        <VerifiedBadge isVerified={profile.isVerified} />\n                    </div>\n                    {teacherBio && (\n                        <p className=\"teacher-profile-bio\">{teacherBio}</p>\n                    )}\n                    <p className=\"teacher-profile-counts\">\n                        {publicCount} 个公开视频\n                        {profile.followersCount !== undefined\n                            ? ` · ${profile.followersCount} 人关注`\n                            : ''}\n                    </p>\n                    {Number(currentUser?.id || 0) !== Number(profile.id) && (\n                        <FollowButton\n                            className=\"teacher-profile-follow\"\n                            userId={profile.id}\n                            variant=\"outline-primary\"\n                        />\n                    )}\n                </aside>\n\n                <main className=\"teacher-course-main\">\n                    {courses.length > 0 && (\n                        <>\n                            <header className=\"teacher-course-heading\">\n                                <h2>系列课程</h2>\n                                <span>\n                                    {courses.length} 个系列 · {videos.length}{' '}\n                                    个已加载视频\n                                </span>\n                            </header>\n                            <div className=\"teacher-course-list\">\n                                {courses.map((course, index) => (\n                                    <details\n                                        className=\"teacher-course-group\"\n                                        key={course.title}\n                                        open={\n                                            expandedCourse === null\n                                                ? index === 0\n                                                : expandedCourse ===\n                                                  course.title\n                                        }\n                                        onToggle={event =>\n                                            setExpandedCourse(\n                                                event.currentTarget.open\n                                                    ? course.title\n                                                    : ''\n                                            )\n                                        }\n                                    >\n                                        <summary>\n                                            <b>\n                                                {String(index + 1).padStart(\n                                                    2,\n                                                    '0'\n                                                )}\n                                            </b>\n                                            <span>\n                                                <strong>{course.title}</strong>\n                                                <small>{course.summary}</small>\n                                            </span>\n                                            <em>\n                                                {course.videos.length\n                                                    ? `${course.videos.length} 讲`\n                                                    : '内容整理中'}\n                                            </em>\n                                            <i className=\"bi bi-chevron-down\" />\n                                        </summary>\n                                        <div className=\"teacher-course-lessons\">\n                                            <p>{course.summary}</p>\n                                            {course.videos.length ? (\n                                                course.videos.map(\n                                                    (video, lessonIndex) => (\n                                                        <a\n                                                            href={appendCurrentChannel(\n                                                                `/video/${video.id}`\n                                                            )}\n                                                            key={video.id}\n                                                        >\n                                                            <b>\n                                                                {String(\n                                                                    lessonIndex +\n                                                                        1\n                                                                ).padStart(\n                                                                    2,\n                                                                    '0'\n                                                                )}\n                                                            </b>\n                                                            <span>\n                                                                {video.title}\n                                                            </span>\n                                                            <time>\n                                                                {video.duration\n                                                                    ? `${Math.ceil(video.duration / 60)} 分钟`\n                                                                    : ''}\n                                                            </time>\n                                                            <i className=\"bi bi-play-circle\" />\n                                                        </a>\n                                                    )\n                                                )\n                                            ) : (\n                                                <p className=\"teacher-course-empty\">\n                                                    课程内容正在整理。\n                                                </p>\n                                            )}\n                                        </div>\n                                    </details>\n                                ))}\n                            </div>\n                        </>\n                    )}\n                    {latestVideos.length > 0 && (\n                        <section className=\"teacher-latest-section\">\n                            <header className=\"teacher-course-heading\">\n                                <h2>最新开示</h2>\n                                <span>{latestVideos.length} 个已加载视频</span>\n                            </header>\n                            <div className=\"teacher-course-lessons teacher-latest-list\">\n                                {latestVideos.map(video => (\n                                    <a\n                                        href={appendCurrentChannel(\n                                            `/video/${video.id}`\n                                        )}\n                                        key={video.id}\n                                    >\n                                        <i className=\"bi bi-play-circle\" />\n                                        <span>{video.title}</span>\n                                        <time>\n                                            {video.duration\n                                                ? `${Math.ceil(video.duration / 60)} 分钟`\n                                                : ''}\n                                        </time>\n                                    </a>\n                                ))}\n                            </div>\n                        </section>\n                    )}\n                    {videos.length === 0 && !videosLoading && (\n                        <p className=\"teacher-course-empty\">\n                            暂无可公开播放的内容。\n                        </p>\n                    )}\n                    {videosLoading && videos.length === 0 && (\n                        <div className=\"text-center mt-4\">\n                            <div\n                                className=\"spinner-border text-primary\"\n                                role=\"status\"\n                            >\n                                <span className=\"visually-hidden\">\n                                    {t('ui.common.loading')}\n                                </span>\n                            </div>\n                        </div>\n                    )}\n                    {hasMore && !videosLoading && videos.length > 0 && (\n                        <button\n                            type=\"button\"\n                            className=\"btn btn-outline-primary teacher-load-more\"\n                            onClick={handleLoadMore}\n                        >\n                            加载更多公开内容\n                        </button>\n                    )}\n                    {hasMore && videos.length > 0 && (\n                        <div\n                            ref={teacherSentinelRef}\n                            className=\"infinite-scroll-sentinel\"\n                        />\n                    )}\n                    {videosLoading && videos.length > 0 && (\n                        <div className=\"text-center mt-4\">\n                            <div\n                                className=\"spinner-border text-primary\"\n                                role=\"status\"\n                            >\n                                <span className=\"visually-hidden\">\n                                    {t('ui.common.loading')}\n                                </span>\n                            </div>\n                        </div>\n                    )}\n                    {!hasMore && !videosLoading && videos.length > 0 && (\n                        <p className=\"teacher-course-empty teacher-course-end\">\n                            暂无更多\n                        </p>\n                    )}\n                </main>\n            </div>\n        );\n    }\n\n    return (\n        <div className=\"user-profile\">\n            {/* 用户头部信息 */}\n            <UserInfo profile={profile} />\n\n            {/* tab */}\n            <Tab.Container activeKey={activeTab} onSelect={handleTabChange}>\n                <div className=\"mb-4\">\n                    <NavTabs\n                        variant=\"default\"\n                        items={[\n                            {\n                                key: 'videos',\n                                label: t('ui.profile.videos', {\n                                    defaultValue: 'Videos',\n                                }),\n                            },\n                            ...(canViewPrivateTabs\n                                ? [\n                                      {\n                                          key: 'comments',\n                                          label: t('ui.profile.comments', {\n                                              defaultValue: 'Comments',\n                                          }),\n                                      },\n                                      {\n                                          key: 'likes',\n                                          label: t('ui.nav.likedVideos'),\n                                      },\n                                  ]\n                                : []),\n                        ]}\n                        activeKey={activeTab}\n                        onTabChange={key => handleTabChange(key)}\n                    />\n                </div>\n\n                <Tab.Content>\n                    <Tab.Pane eventKey=\"videos\">\n                        <InfiniteScrollVideoGrid\n                            videos={videos}\n                            loading={videosLoading}\n                            error={error}\n                            hasMore={hasMore && activeTab === 'videos'}\n                            onLoadMore={handleLoadMore}\n                            onVideoClick={handleVideoClick}\n                            loadMoreButtonText={t('ui.button.loadMore', {\n                                defaultValue: 'Load More Videos',\n                            })}\n                            threshold={200}\n                            hideAuthor={true}\n                            renderEmpty={() => (\n                                <Row className=\"video-grid\">\n                                    <Col>\n                                        <div className=\"empty-state\">\n                                            <i className=\"bi bi-camera-video\"></i>\n                                            <h5>\n                                                {t('ui.empty.noVideos', {\n                                                    defaultValue:\n                                                        'No videos yet',\n                                                })}\n                                            </h5>\n                                            <p>\n                                                {t('ui.empty.noVideosUser', {\n                                                    defaultValue:\n                                                        \"This user hasn't uploaded any videos.\",\n                                                })}\n                                            </p>\n                                        </div>\n                                    </Col>\n                                </Row>\n                            )}\n                        />\n                    </Tab.Pane>\n\n                    {canViewPrivateTabs && (\n                        <>\n                            <Tab.Pane eventKey=\"comments\">\n                                <UserComments\n                                    userId={profile.id}\n                                    shouldLoad={activeTab === 'comments'}\n                                />\n                            </Tab.Pane>\n                            <Tab.Pane eventKey=\"likes\">\n                                <UserLikes\n                                    userId={profile.id}\n                                    shouldLoad={activeTab === 'likes'}\n                                />\n                            </Tab.Pane>\n                        </>\n                    )}\n                </Tab.Content>\n            </Tab.Container>\n        </div>\n    );\n}\n\nexport default UserProfile;\n","import UserProfile, { UserProfileProps } from './userprofile';\nimport { render } from '../../libs';\nimport { I18nProvider } from '../../libs/i18n';\n\n// 包装组件以提供 i18n 支持\nfunction UserProfilePageApp(props: UserProfileProps) {\n    return (\n        <I18nProvider>\n            <UserProfile {...props} />\n        </I18nProvider>\n    );\n}\n\n// 从DOM中读取属性并渲染组件\nconst renderUserProfile = (containerId: string) => {\n    const container = document.getElementById(containerId);\n    if (!container) return;\n\n    const username = container.getAttribute('data-username');\n    const initialTab = container.getAttribute('data-active-tab');\n\n    if (!username) return;\n\n    render(containerId, () => (\n        <UserProfilePageApp\n            username={username}\n            initialTab={initialTab || undefined}\n        />\n    ));\n};\n\nrenderUserProfile('userprofile-container');\n"],"names":["NavTabs","props","t","useTranslation","items","activeKey","onTabChange","className","variant","showLeftArrow","showRightArrow","onScrollArrowClick","scrollContainerRef","handleTabClick","key","jsxs","classNames","jsx","item","userinfo","profile","currentUser","useAuth","UserAvatar","VerifiedBadge","FollowButton","dayjs","timezone","utc","Index","time","preFix","formatTime","from","now","between","min","h","FormatTime","memo","UserComments","userId","shouldLoad","comments","setComments","useState","loading","setLoading","error","setError","loadUserComments","queryParams","data","api","err","getErrorMessage","useEffect","Alert","appendCurrentChannel","comment","UserLikes","masterSlug","master","getMasterProfile","likes","setLikes","loadUserLikes","Fragment","Row","like","index","Col","lessonNumber","title","courseTitle","groupTeacherCourseVideos","videos","groups","latestVideos","video","lessons","left","right","UserProfile","username","initialTab","setProfile","setVideos","videosLoading","setVideosLoading","currentPage","setCurrentPage","hasMore","setHasMore","activeTab","setActiveTab","expandedCourse","setExpandedCourse","videosInitialized","useRef","loadUserProfile","teacherProfiles","teacher","loadUserVideos","response","useCallback","page","newVideos","pagination","previousVideos","handleLoadMore","teacherSentinelRef","useInfiniteScroll","handleVideoClick","appPath","handleTabChange","tabKey","url","canViewPrivateTabs","teacherProfile","courses","publicCount","teacherBio","course","event","lessonIndex","UserInfo","Tab","InfiniteScrollVideoGrid","UserProfilePageApp","I18nProvider","renderUserProfile","containerId","container","render"],"mappings":"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"}