import { contentPath } from '../routing'; import type { Chapter, Config, Drama, DramaDetail, HomeData } from '../api/types'; import { recordList } from '../api/protocol'; import { ads, card, empty, escape, imageAttributes, promotionRow, title } from './render'; import { descriptionBlock, engageButtons, factsRow, hashTags, isSingleVideo, officialLinks, overviewMarkup, statusLabel } from './detail'; import { chapterAccess, chapterListGate, chapterRequiresPayment, initialPlayMode, INLINE_VIDEO_ATTRIBUTES, playbackUrl } from './playback'; import { downloadUrl } from '../app-download'; import { SITE_NAME } from '../brand'; /** Recommendation shelf size (guess-like list, current item excluded). */ export const RECOMMEND_PAGE_SIZE=12; export interface PlayerViewOptions { chapter?:Chapter|null; isVideo?:boolean; config?:Config; home?:HomeData; } export function orderedChapters(value:unknown):Chapter[] { return recordList(value).filter(item=>Number.isSafeInteger(item.id)&&item.id>0).sort((a,b)=>(a.currentEpisode||0)-(b.currentEpisode||0)); } const lockIcon=''; const arrow=(direction:'prev'|'next')=>``; /** Locked episodes are watched in the App: overlay inside the player with a download button. */ export function appGateMarkup(drama:Drama,config:Config={}):string { const url=downloadUrl(config); return ``; } /** "推荐视频" shelf: guess-like items, current drama excluded. */ export function recommendationsMarkup(raw:DramaDetail):string { const items=recordList(raw.relatedRecommendData).filter(item=>item.id!==raw.id).slice(0,RECOMMEND_PAGE_SIZE); return `

推荐视频

看了《${escape(title(raw))}》的人还在看

${items.length?`
${items.map(item=>card(item)).join('')}
`:empty('暂无推荐视频')}
`; } /** One horizontal film strip with every episode; locked tiles still link to their play page. */ export function episodeItems(id:number,ordered:Chapter[],chapter:Chapter|null,slug?:string):string { return ordered.map((item,index)=>{ const number=item.currentEpisode||index+1; const locked=!!chapterListGate(item),current=item.id===chapter?.id; return `${String(number).padStart(2,'0')}${locked?lockIcon:''}`; }).join('')||empty('暂无剧集'); } export function episodeStrip(raw:DramaDetail,ordered:Chapter[],chapter:Chapter|null):string { const status=statusLabel(raw); return `

选集

全${raw.totalEpisode||ordered.length}集${status?` · ${status}`:''}
${episodeItems(raw.id,ordered,chapter,raw.slug)}
`; } export function playerMarkup(raw:DramaDetail,ordered:Chapter[],options:PlayerViewOptions={}):string { const {chapter=null,isVideo=false,config={},home={}}=options; const overview=!chapter&&!isVideo,first=ordered.find(item=>item.currentEpisode===1)||ordered[0]; const single=isVideo||isSingleVideo(raw,ordered); const displayTitle=title(raw),heading=chapter&&!single?`${displayTitle} - 第${chapter.currentEpisode||1}集`:displayTitle; const access=isVideo?{allowed:!!playbackUrl(raw.video?.url),gate:playbackUrl(raw.video?.url)?'':'unavailable',source:playbackUrl(raw.video?.url)} as const:chapterAccess(chapter); const previewSource=isVideo?'':playbackUrl(raw.preview),mode=initialPlayMode(access,raw.preview); const modeTabs=previewSource?`
`:''; const position=chapter?ordered.findIndex(item=>item.id===chapter.id):-1; const previous=!single&&position>0?ordered[position-1]:undefined,next=!single&&position>=0?ordered[position+1]:undefined; const edge=(item:Chapter|undefined,direction:'prev'|'next')=>item?`${arrow(direction)}`:''; const screen=()=>`${modeTabs}

正在准备视频…

${appGateMarkup(raw,config)}
${edge(previous,'prev')}${edge(next,'next')}
`; const mark=single?'VIDEO':String(chapter?.currentEpisode||1).padStart(2,'0'); const headline=()=>`
${single?'视频':`第 ${chapter?.currentEpisode||1} 集 / 共 ${raw.totalEpisode||ordered.length} 集`}

${escape(heading)}

${factsRow(raw,ordered)}${hashTags(raw)}${descriptionBlock(raw)}${engageButtons(raw)}
`; const applicationAds=promotionRow(raw.advitise?.nineGridList||home.advitise?.nineGridList); const bannerAds=ads(raw.advitise?.detailbannerList,'play-banner-ad'); return `
${overview?overviewMarkup(raw,first,ordered):screen()} ${overview?'':headline()} ${applicationAds?`
${applicationAds}
`:''} ${!single?episodeStrip(raw,ordered,chapter):''} ${bannerAds?`
${bannerAds}
`:''} ${recommendationsMarkup(raw)} ${officialLinks(config)}
`; }