整体UI修改
This commit is contained in:
+15
-66
@@ -1,20 +1,18 @@
|
||||
import { contentPath } from '../routing';
|
||||
import type { Chapter, Comment, Drama, DramaDetail, PageData } from "../api/types";
|
||||
import type { Chapter, Drama, DramaDetail, PageData } from "../api/types";
|
||||
import type { SiteContext } from "./context";
|
||||
import { empty, escape } from "./render";
|
||||
import { configureDownloadLinks } from "./download";
|
||||
import { bindSeriesDescription } from "./detail";
|
||||
import { bindChapterUnlock } from "./unlock";
|
||||
import { chapterAccess, chapterUnlockCost, initialPlayMode, MediaPlayback, playbackUrl, type PlayMode, type PlayerActions } from './playback';
|
||||
import { chapterAccess, initialPlayMode, MediaPlayback, playbackUrl, type PlayMode, type PlayerActions } from './playback';
|
||||
import { dramaSeoContent } from '../seo';
|
||||
import type { PlayerInitialData } from '../page-data';
|
||||
import { commentListMarkup, episodeItems, episodeTabs, orderedChapters, playerMarkup } from './player-markup';
|
||||
import { episodeItems, episodeTabs, orderedChapters, playerMarkup } from './player-markup';
|
||||
import { formatCount } from '../api/protocol';
|
||||
import { applyEngagementSuccess, beginButtonRequest, type EngagementKind } from './request-feedback';
|
||||
|
||||
declare global { interface Window { installPlayerControls?: (screen: HTMLElement, episode?: number, actions?: PlayerActions)=>void } }
|
||||
|
||||
interface PlayerPagination {page?:number;commentSort?:number;pageHref?:(page:number,sort:number)=>string;initial?:PlayerInitialData;preserveMarkup?:boolean}
|
||||
interface PlayerPagination {initial?:PlayerInitialData;preserveMarkup?:boolean}
|
||||
|
||||
export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,isVideo=false,paging:PlayerPagination={}):Promise<()=>void> {
|
||||
if(isVideo)throw new Error('当前接口暂未提供剧集内容');
|
||||
@@ -25,7 +23,7 @@ export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,i
|
||||
if(!raw?.id||raw.id!==id)throw new Error('该剧集不存在或已下架');
|
||||
const [chapters,related]=await Promise.all([
|
||||
paging.initial?Promise.resolve(paging.initial.chapters):app.api.get<{list?:Chapter[]}>('playlet/chapter/list',{playletId:id},signal).then(data=>data?.list).catch(error=>{if(signal.aborted)throw error;return raw.playletChapterInfo;}),
|
||||
paging.initial?Promise.resolve(undefined):app.api.get<PageData<Drama>>('playlet/guess-like/list',{playletId:id,pageNum:1,pageSize:6},signal).catch(error=>{if(signal.aborted)throw error;return undefined;}),
|
||||
paging.initial?Promise.resolve(undefined):app.api.get<PageData<Drama>>('playlet/guess-like/list',{playletId:id,pageNum:1,pageSize:12},signal).catch(error=>{if(signal.aborted)throw error;return undefined;}),
|
||||
]);
|
||||
if(!paging.initial)raw.relatedRecommendData=related?.list||[];
|
||||
if(signal.aborted)return ()=>{};
|
||||
@@ -39,17 +37,13 @@ export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,i
|
||||
}
|
||||
if(signal.aborted)return ()=>{};
|
||||
const overview=!chapterId&&!isVideo;
|
||||
const unlockCost=chapterUnlockCost(chapter);
|
||||
const source=isVideo?playbackUrl(raw.video?.url):'';
|
||||
const access=isVideo?{allowed:!!source,gate:source?'':'unavailable',source} as const:chapterAccess(chapter);
|
||||
const previewSource=isVideo?'':playbackUrl(raw.preview);
|
||||
let mode:PlayMode=initialPlayMode(access,raw.preview);
|
||||
page.classList.toggle('is-overview',overview);
|
||||
const commentPage=paging.page||1,commentSort=paging.commentSort===1?1:2;
|
||||
const basePath=contentPath(isVideo?'video':chapterId?'play':'detail',raw,chapterId);
|
||||
const pageHref=paging.pageHref||((page:number,sort:number)=>`${basePath}${page>1?`page/${page}/`:''}${sort===1?'?commentSort=1':''}`);
|
||||
app.updateSeo?.({...dramaSeoContent(raw,chapter||undefined),failed:false,unavailable:isVideo&&!source});
|
||||
const objectId=isVideo?id:chapter?.id||id, objectType=isVideo?20:chapter?11:10;
|
||||
const objectId=isVideo?id:chapter?.id||id;
|
||||
const actionPrefix=isVideo?'video':chapter?'playlet/chapter':'playlet';
|
||||
const actionState=chapter||raw;
|
||||
const pendingEngagements=new Set<EngagementKind>();
|
||||
@@ -62,9 +56,8 @@ export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,i
|
||||
const value=kind==='like'?actionState.countLike:actionState.countCollect;
|
||||
page.querySelectorAll<HTMLElement>(`[data-engage-count="${kind}"]`).forEach(node=>{node.textContent=`${formatCount(value)}${kind==='like'?'点赞':'收藏'}`;});
|
||||
}
|
||||
if(!paging.preserveMarkup)page.innerHTML=playerMarkup(raw,ordered,{chapter,isVideo,page:commentPage,commentSort,pageHref,config:app.config,home:app.home,user:app.api.user,comments:paging.initial?.comments});
|
||||
if(!paging.preserveMarkup)page.innerHTML=playerMarkup(raw,ordered,{chapter,isVideo,home:app.home});
|
||||
configureDownloadLinks(app.config, page);
|
||||
window.tracker?.setDrama({...raw,playletChapterInfo:ordered},chapter||first,ordered,isVideo);
|
||||
const disposeDescription=bindSeriesDescription(page,signal);
|
||||
let episodeSize=matchMedia('(max-width:760px)').matches?15:45;
|
||||
let episodePage=chapter?Math.floor(Math.max(0,ordered.findIndex(item=>item.id===chapter!.id))/episodeSize):0;
|
||||
@@ -82,53 +75,32 @@ export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,i
|
||||
if(tab.left<bounds.left)track.scrollLeft+=tab.left-bounds.left;
|
||||
else if(tab.right>bounds.right)track.scrollLeft+=tab.right-bounds.right;
|
||||
}
|
||||
grid.innerHTML=episodeItems(id,ordered,chapter,size,episodePage,raw.slug);
|
||||
grid.innerHTML=episodeItems(id,ordered,chapter,size,episodePage,raw.slug,raw.title||raw.playName||'');
|
||||
}
|
||||
renderEpisodes();
|
||||
window.addEventListener('resize',renderEpisodes,{signal});
|
||||
let commentSeq=0,replyParent=0,replyId=0;
|
||||
let initialComments=paging.initial?.comments;
|
||||
async function comments(){
|
||||
const seq=++commentSeq;
|
||||
const list=page.querySelector<HTMLElement>('.play-comment-list')!;
|
||||
try{
|
||||
const existing=initialComments;initialComments=undefined;
|
||||
const data=existing || await app.api.get<PageData<Comment>>('comment/list',{objectId,objectType,pageNum:commentPage,pageSize:10,sortOrder:commentSort},signal);
|
||||
if(signal.aborted||seq!==commentSeq)return;
|
||||
app.updateSeo?.({totalCount:data.totalCount,pageSize:10});
|
||||
page.querySelector('#play-comments-title')!.textContent=`评论 (${data.totalCount||0})`;
|
||||
if(!(existing&&paging.preserveMarkup))list.innerHTML=commentListMarkup(data,commentPage,commentSort,pageHref);
|
||||
}catch(error){if(!signal.aborted&&seq===commentSeq)list.innerHTML=empty(error instanceof Error?error.message:'评论加载失败');}
|
||||
}
|
||||
void comments();
|
||||
const video=page.querySelector<HTMLVideoElement>('video');
|
||||
const chapterUnlock=bindChapterUnlock(app,id,chapter,raw.slug);
|
||||
let media:MediaPlayback|undefined;
|
||||
let reported=false;
|
||||
if(video){
|
||||
const box=page.querySelector<HTMLElement>('.play-screen')!;
|
||||
const status=page.querySelector<HTMLElement>('.play-source-status')!;
|
||||
media=new MediaPlayback(video,{signal,status:message=>{status.textContent=message;},error:()=>{window.tracker?.playbackFailed(video);box.dispatchEvent(new Event('playback:error'));}});
|
||||
const toggle=async()=>{if(mode==='main'&&!access.allowed){chapterUnlock.openCurrent();return;}await media?.toggle();};
|
||||
window.installPlayerControls?.(box,chapter?.currentEpisode||1,{toggle,retry:async()=>{if(mode==='main'&&!access.allowed){chapterUnlock.openCurrent();return;}window.tracker?.prepareDramaModeChange();await media?.retry();}});
|
||||
media=new MediaPlayback(video,{signal,status:message=>{status.textContent=message;},error:()=>{box.dispatchEvent(new Event('playback:error'));}});
|
||||
const toggle=async()=>{if(mode==='main'&&!access.allowed)return;await media?.toggle();};
|
||||
window.installPlayerControls?.(box,chapter?.currentEpisode||1,{toggle,retry:async()=>{if(mode==='main'&&!access.allowed)return;await media?.retry();}});
|
||||
const position=ordered.findIndex(item=>item.id===chapter?.id);
|
||||
const previous=box.querySelector<HTMLButtonElement>('[data-play-action="previous"]');
|
||||
const next=box.querySelector<HTMLButtonElement>('[data-play-action="next"]');
|
||||
if(previous)previous.disabled=position<=0;if(next)next.disabled=position<0||position>=ordered.length-1;
|
||||
const unlock=document.createElement('button');unlock.type='button';unlock.className='primary';unlock.dataset.showUnlock='';
|
||||
unlock.textContent=access.gate==='vip'?'查看会员套餐':`使用 ${unlockCost} 金币解锁`;
|
||||
box.querySelector('.play-screen-overlay')!.append(unlock);
|
||||
function setMode(nextMode:PlayMode){
|
||||
window.tracker?.prepareDramaModeChange();
|
||||
mode=nextMode;box.dataset.playMode=mode;
|
||||
page.querySelectorAll<HTMLButtonElement>('[data-play-mode]').forEach(button=>{if(button.tagName==='BUTTON')button.setAttribute('aria-pressed',String(button.dataset.playMode===mode));});
|
||||
const blocked=mode==='main'&&!access.allowed;
|
||||
unlock.hidden=!blocked||!['coins','vip'].includes(access.gate);
|
||||
box.querySelector('.ui-empty-state')?.remove();
|
||||
const center=box.querySelector<HTMLButtonElement>('[data-media="toggle"]');if(center)center.hidden=false;
|
||||
status.hidden=false;
|
||||
void media!.setSource(mode==='preview'?previewSource:access.allowed?access.source:'');
|
||||
if(blocked)status.textContent=access.gate==='vip'?'开通会员后可观看本集':access.gate==='coins'?'本集需要金币解锁':'本集暂不可观看';
|
||||
if(blocked)status.textContent='本集视频暂未开放,请稍后再试';
|
||||
else if(!(mode==='preview'?previewSource:access.source))status.textContent='该剧集暂未提供视频源';
|
||||
else if(mode==='preview')status.textContent='免费预告 · 点击播放';
|
||||
}
|
||||
@@ -143,11 +115,9 @@ export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,i
|
||||
}
|
||||
page.addEventListener('click',event=>{void (async()=>{
|
||||
const element=(event.target as Element).closest<HTMLButtonElement>('button');if(!element||element.disabled)return;
|
||||
if(element.dataset.lockedChapter){const selected=ordered.find(item=>item.id===Number(element.dataset.lockedChapter));if(selected)await chapterUnlock.openChapter(selected);return;}
|
||||
if(element.dataset.episodePage){episodePage=Number(element.dataset.episodePage);renderEpisodes();}
|
||||
if(element.dataset.episodePage==='0'){episodePage=0;renderEpisodes();}
|
||||
if(element.hasAttribute('data-show-unlock'))chapterUnlock.openCurrent();
|
||||
if(element.dataset.playAction){const index=ordered.findIndex(item=>item.id===chapter?.id);const next=ordered[index+(element.dataset.playAction==='next'?1:-1)];if(next){window.tracker?.dramaStatusChange(next);app.navigate(contentPath('play',raw,next.id));}}
|
||||
if(element.dataset.playAction){const index=ordered.findIndex(item=>item.id===chapter?.id);const next=ordered[index+(element.dataset.playAction==='next'?1:-1)];if(next){app.navigate(contentPath('play',raw,next.id));}}
|
||||
if(element.dataset.engage){
|
||||
const kind=element.dataset.engage;if(kind!=='like'&&kind!=='collect'||pendingEngagements.has(kind))return;
|
||||
const active=kind==='like'?actionState.isLike===1:actionState.isCollect===1;
|
||||
@@ -162,28 +132,7 @@ export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,i
|
||||
}finally{pendingEngagements.delete(kind);finish.forEach(done=>done());}
|
||||
return;
|
||||
}
|
||||
if(element.hasAttribute('data-share')){const finish=beginButtonRequest(element,'复制中');try{await navigator.clipboard.writeText(location.href);if(!signal.aborted){window.tracker?.dramaEvent('ep_share');app.notify('页面链接已复制');}}finally{finish();}return;}
|
||||
if(element.dataset.commentSort){const sort=Number(element.dataset.commentSort);if(sort===1||sort===2)app.navigate(pageHref(1,sort));return;}
|
||||
if(element.dataset.reply){replyId=Number(element.dataset.reply);replyParent=Number(element.dataset.parent);page.querySelector('#reply-context')!.innerHTML=`<div class="play-reply-context"><span>回复 @${escape(element.dataset.replyName)}</span><button type="button" data-cancel-reply>取消回复</button></div>`;page.querySelector<HTMLTextAreaElement>('textarea')!.focus();}
|
||||
if(element.hasAttribute('data-cancel-reply')){replyId=replyParent=0;page.querySelector('#reply-context')!.textContent='';}
|
||||
if(element.dataset.commentLike){
|
||||
const active=element.getAttribute('aria-pressed')==='true',state={isLike:active?1:0,countLike:Number(element.dataset.commentCount)||0};
|
||||
const finish=beginButtonRequest(element);
|
||||
try{
|
||||
const result=await app.api.post<unknown>('comment/like/set',{commentId:Number(element.dataset.commentLike),isLike:!active});
|
||||
if(signal.aborted)return;
|
||||
applyEngagementSuccess(state,'like',active,result);
|
||||
element.setAttribute('aria-pressed',String(state.isLike===1));element.dataset.commentCount=String(state.countLike);
|
||||
const label=element.querySelector('[data-request-label]')||element;label.textContent=`${state.isLike===1?'♥':'♡'} ${formatCount(state.countLike)}`;
|
||||
}finally{finish();}
|
||||
}
|
||||
if(element.hasAttribute('data-share')){const finish=beginButtonRequest(element,'复制中');try{await navigator.clipboard.writeText(location.href);if(!signal.aborted)app.notify('页面链接已复制');}finally{finish();}return;}
|
||||
})().catch(error=>app.notify(error instanceof Error?error.message:'操作失败'));},{signal});
|
||||
page.querySelector('textarea[name="comment"]')?.addEventListener('input',event=>{const counter=page.querySelector('#play-comment-count');if(counter)counter.textContent=String((event.target as HTMLTextAreaElement).value.length);},{signal});
|
||||
page.querySelector('form')!.addEventListener('submit',event=>{
|
||||
event.preventDefault();const form=event.target as HTMLFormElement;const input=form.elements.namedItem('comment') as HTMLTextAreaElement;const button=form.querySelector<HTMLButtonElement>('[type="submit"]')!;
|
||||
if(button.disabled||!input.value.trim())return;
|
||||
const finish=beginButtonRequest(button,'发送中');
|
||||
void app.api.post('comment/send',{objectId,objectType,text:input.value.trim(),parentsId:replyParent,replyCommentId:replyId}).then(async()=>{if(signal.aborted)return;input.value='';const counter=page.querySelector('#play-comment-count');if(counter)counter.textContent='0';replyId=replyParent=0;page.querySelector('#reply-context')!.textContent='';page.querySelector('#comment-error')!.textContent='';if(commentPage>1){app.navigate(pageHref(1,commentSort));return;}await comments();}).catch(error=>{if(!signal.aborted)page.querySelector('#comment-error')!.textContent=error.message;}).finally(finish);
|
||||
},{signal});
|
||||
return ()=>{disposeDescription();chapterUnlock.dispose();media?.dispose();};
|
||||
return ()=>{disposeDescription();media?.dispose();};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user