Merge branch 'preview-fixes'

This commit is contained in:
the1812 2023-08-26 12:44:35 +08:00
commit 7797af6344
35 changed files with 1164 additions and 726 deletions

View File

@ -1,10 +1,12 @@
{
"name": "Build",
"on": {
"workflow_dispatch": null,
"push": {
"branches": [
"master",
"master-cdn"
"master-cdn",
"preview"
]
}
},
@ -13,11 +15,15 @@
"runs-on": "ubuntu-latest",
"steps": [
{
"uses": "actions/checkout@v2",
"uses": "actions/checkout@v3",
"with": {
"fetch-depth": "0"
}
},
{
"name": "Setup git",
"run": "git config --local user.email github-actions[bot]@users.noreply.github.com\ngit config --local user.name github-actions[bot]\ngit config --global core.autocrlf true\ngit config --global core.safecrlf false"
},
{
"name": "Install dependencies",
"uses": "pnpm/action-setup@v2.2.4",
@ -49,7 +55,7 @@
{
"name": "Git commit",
"id": "commit",
"run": "git config --local user.email github-actions[bot]@users.noreply.github.com\ngit config --local user.name github-actions[bot]\ngit config --global core.autocrlf true\ngit config --global core.safecrlf false\ngit add --all\ngit commit -m \"CI build\"\n",
"run": "git add --all\ngit commit -m \"CI build\"\n",
"continue-on-error": true
},
{

View File

@ -1,5 +1,106 @@
# 更新日志
## v2.8.3-preview
`2023-08-26`
包含 v2.7.5 的所有更新内容.
✨新增
- `网址参数清理` 更新对直播间的支持. (#1459)
- `自动点赞` 转为由 [CrazyboyQCD](https://github.com/CrazyboyQCD) 维护, 支持手动控制点赞和黑名单功能. (PR #4343, PR #4358 by [CrazyboyQCD](https://github.com/CrazyboyQCD))
- `简化直播间` 支持隐藏荣耀等级勋章. (PR #4348 by [CrunchyShark](https://github.com/CrunchyShark944))
- 在设置面板中的组件管理等面板中, 搜索框移动至已安装列表的上方, 更符合直觉. (#3806)
- 支持从 Tampermonkey 的菜单中唤起功能和设置面板. (#4170)
- 搜索栏中添加了导入 / 导出设置的操作. (#4170)
- `自定义顶栏` 新增选项 `链接对齐样式`, 可以自定义纯链接弹窗内链接文字的对齐样式. (PR #4365 by [Tinhone](https://github.com/Tinhone))
- `简化评论区` 支持独立控制简化选项, 并支持隐藏粉丝勋章和活动横幅. (仅支持新版评论区) (#2381)
- `清爽首页` 设置为隐藏的版块现在将彻底销毁, 减少资源占用.
- `禁用特殊弹幕样式` 支持禁用大会员弹幕. (#4227)
- `下载视频` 在批量下载番剧时, 支持下载多个不同板块的选集. (#2834)
- 新增功能 `全屏直播礼物简化`. (PR #4306 by [TimmyOVO](https://github.com/TimmyOVO))
> 移除全屏观看直播时的底部礼物栏
- 新增功能 `评论区 IP 属地显示`. (PR #4331, PR #4334 by [Light_Quanta](https://github.com/LightQuanta))
> 在评论区显示评论的IP属地信息
- 新增功能 `直播间网页全屏自适应`. (#4216)
> 在直播网页全屏时, 自动调整侧边栏的宽度, 使得视频区域的比例和视频源相匹配, 达到无黑边的效果.
>
> - `侧边栏最大宽度 (px)`: 限制侧边栏可被拉伸到的最大宽度. (最小宽度固定为 190px, 再小的话布局就要出问题了)
>
> > 注意, 由于有最大宽度和最小宽度的限制, 部分窗口尺寸下仍然无法做到无黑边.
- 新增功能 `关注时间显示`. (PR #4352 by [Light_Quanta](https://github.com/LightQuanta))
> 在个人空间的粉丝/关注列表显示关注的具体时间
- 新增功能 `自动移出稍后再看`.
> 在稍后再看页面播放结束时, 自动将当前视频移出稍后再看.
> 注意:
> - 一定要播放结束, 快结束时手动切走不算
> - b 站的稍后再看列表不会实时刷新
- 新增功能 `相簿发布时间显示`. (PR #4362 by [Light_Quanta](https://github.com/LightQuanta))
> 在个人空间的相簿界面显示相簿的发布时间
🐛修复
- 修复 `清爽首页` 番剧区图标不显示. (#4262)
- 修复 `清爽首页` 番剧区时间线的遮罩颜色在夜间模式下不正确.
- 修复 `隐藏记笔记` 功能打开后, 评论区的笔记弹窗无法显示. (#4285)
☕开发者相关
- SwitchOptions API 更新:
- 老的 `createSwitchOptions` 标记为已弃用.
- 新的 `newSwitchComponentWrapper` 更名为 `wrapSwitchOptions`.
- `checkedIcon` 拥有默认值: `mdi-eye-off-outline`.
- `notCheckedIcon` 拥有默认值: `mdi-eye-outline`.
- `dimAt` 选项功能更改为如下所示, 更符合直觉:
```ts
/**
* 控制开关变暗的时机
*
* `false`: 始终不变暗
* `'checked'`: 当开关开启时变暗
* `'notChecked'`: 当开关关闭时变暗
*
* @default 'checked'
*/
dimAt?: false | 'checked' | 'notChecked'
```
- Vue 升级到 2.7, 支持组合式 API 以及使用 Volar 提供代码提示. (PR #4337 by [timongh](https://github.com/timongh))
- 修复组件加载失败时没有输出任何报错.
## v2.7.5
`2023-08-26`
✨新增
- 夜间模式适配新版番剧播放页面. (PR #4366 by [QwExZy@0xx1](https://github.com/qwaszx-WXY))
🐛修复
- 修复下载弹幕的数量比实际要少. (#4287)
- jsDelivr 源域名由 `fastly.jsdelivr.net` 更换为 `cdn.jsdelivr.net`. (#4198)
- 修复 `快速收起评论区` 在新版动态下宽度不正确. (#4282)
- 修复删除搜索历史时选项不会立即消失. (#4268)
- 修复 `直播全屏包裹` 的颜色问题. (#4166, PR #4323 by [CrazyboyQCD](https://github.com/CrazyboyQCD))
- 修复 `下载字幕` 失效, 注意由于 b 站接口变更, AI 字幕可能无法下载. (#4319)
- 修复 `隐藏视频推荐` 未能隐藏番剧页面的相关推荐. (#4247)
- 修复 `自定义顶栏` 中封面图片在个人空间的样式异常. (#4312, PR #4338 by [timongh](https://github.com/timongh))
- 修复 `简化直播间` 屏蔽标题栏活动后, 粉丝团打榜弹窗位置异常. (#4215)
- 修复 `传统连播模式` 在点击右侧推荐视频时会延后一次. (#4256)
☕开发者相关
- 修复 VPopup 组件中 openHandler 的 this 绑定丢失. (#4264)
- 重构了 Comments API. (#4334)
- `CommentReplyItem` 实现了 `EventTarget`, 评论更新事件 (`repliesUpdate`) 均通过 `EventTarget` 的事件订阅实现.
- 修复新版评论区下评论更新事件没触发.
- 重构了 `playerModePolyfill` 的实现, 解决 empty class token 在页面上反复报错的问题.
## v2.7.4 / v2.8.2-preview
`2023-07-16`

View File

@ -91,7 +91,7 @@
不兼容.
### [AdGuard](https://adguard.com/zh_cn/adguard-windows/overview.html)
未测试.
不兼容.
## 浏览器

File diff suppressed because it is too large Load Diff

View File

@ -811,7 +811,7 @@
"type": "component",
"name": "downloadSubtitle",
"displayName": "下载字幕",
"description": "启用下载字幕支持, 在视频页面中可从功能面板里下载字幕.",
"description": "启用下载字幕支持, 在视频页面中可从功能面板里下载字幕. (AI 生成的不可下载)\r\n",
"fullRelativePath": "../../registry/dist/components/video/subtitle/download.js",
"fullAbsolutePath": "registry/dist/components/video/subtitle/download.js"
},

File diff suppressed because it is too large Load Diff

View File

@ -131,7 +131,7 @@
"type": "component",
"name": "downloadSubtitle",
"displayName": "下载字幕",
"description": "启用下载字幕支持, 在视频页面中可从功能面板里下载字幕.",
"description": "启用下载字幕支持, 在视频页面中可从功能面板里下载字幕. (AI 生成的不可下载)\r\n",
"fullRelativePath": "../../registry/dist/components/video/subtitle/download.js",
"fullAbsolutePath": "registry/dist/components/video/subtitle/download.js"
},

View File

@ -4,9 +4,8 @@
position: sticky;
bottom: 0;
height: 40px;
width: calc(100% + 48px);
width: 100%;
font-size: 14px;
transform: translateX(-24px);
display: flex;
align-items: center;
justify-content: center;

View File

@ -54,3 +54,23 @@
}
}
}
.new-silver-wallet {
background-color: var(--Ga1_u) !important;
}
.new-list-box * .num > span {
color: var(--text3) !important;
}
.click-root > .content-box {
color: #000 !important;
> .text {
color: #000 !important;
}
}
.new-list-box * .static-bg-root {
--static-bg-color: #ffecf1 !important;
--static-bg-border-color: #ff6699 !important;
}

View File

@ -22,7 +22,7 @@
<transition-group v-else name="cards" tag="div" class="cards">
<div v-for="card of filteredCards" :key="card.id" class="favorite-card">
<a
class="cover-container"
class="favorites-cover-container"
target="_blank"
:href="'https://www.bilibili.com/video/' + card.bvid"
>
@ -376,7 +376,7 @@ export default Vue.extend({
&:hover .cover {
transform: scale(1.05);
}
.cover-container {
.favorites-cover-container {
grid-area: cover;
overflow: hidden;
border-radius: 8px 0 0 8px;

View File

@ -48,7 +48,7 @@
</div>
<transition-group name="time-group" tag="div" class="time-group-items">
<div v-for="h of g.items" :key="h.id" class="time-group-item">
<a class="cover-container" target="_blank" :href="h.url">
<a class="history-cover-container" target="_blank" :href="h.url">
<DpiImage
class="cover"
:src="h.cover"
@ -381,7 +381,7 @@ export default Vue.extend({
opacity: 1;
}
}
.cover-container {
.history-cover-container {
$height: 55px;
$padding: 2px;
grid-area: cover;

View File

@ -11,7 +11,7 @@
:href="card.playUrl"
target="_blank"
>
<div class="cover-container">
<div class="subscriptions-cover-container">
<DpiImage class="cover" :src="card.coverUrl" :size="64"></DpiImage>
</div>
<div class="card-info">
@ -186,7 +186,7 @@ export default Vue.extend({
background-color: #2d2d2d;
color: #eee;
}
.cover-container {
.subscriptions-cover-container {
height: 64px;
width: 64px;
border-radius: $radius 0 0 $radius;

View File

@ -24,7 +24,7 @@
<VEmpty v-else-if="!loading && cards.length === 0"></VEmpty>
<transition-group v-else name="cards" tag="div" class="watchlater-list-content">
<div v-for="(card, index) of filteredCards" :key="card.aid" class="watchlater-card">
<a class="cover-container" target="_blank" :href="card.href">
<a class="watchlater-cover-container" target="_blank" :href="card.href">
<DpiImage
class="cover"
:src="card.coverUrl"
@ -285,7 +285,7 @@ export default Vue.extend({
&:hover .cover {
transform: scale(1.05);
}
.cover-container {
.watchlater-cover-container {
grid-area: cover;
overflow: hidden;
border-radius: 8px 0 0 8px;

View File

@ -350,6 +350,9 @@ a {
.main-container .bottom-brief-introduction {
@include background-color("2");
}
.main-container {
@include background-color();
}
.fullscreen-loading .tv-bg + .container {
@include background-color();
.retry-btn {
@ -772,7 +775,7 @@ body:not(.simplify-comment) .panel-area .bb-comment {
}
}
.z-top-container {
@include background-color("3");
@include background-color("2");
}
.coin-dialog-wrapper {
@include background-color("2");

View File

@ -860,6 +860,9 @@
}
.bili-comment.browser-pc {
@include background-color();
.comment-container {
@include background-color();
}
}
.recommended-container,
.recommended-container_floor-aside {

View File

@ -571,3 +571,201 @@
}
}
}
.home-cell-desc-title {
@include color('e');
}
.home-cell-desc-subtitle {
@include color('a');
}
.timeline-weekday-item .weekday-description .timeline-item-title {
@include color('e');
}
#{contains('recommend_wrap')} {
#{contains('recommend_title')} {
@include color('e');
}
}
.section-title-v2-wrapper {
.title-right {
@include border-color('4');
.font {
@include color('e');
}
&:hover .font {
@include color('0');
}
&:not(:hover) {
img,
svg {
@include to-white();
}
}
}
.title-left {
.week-day-wrapper {
@include background-color('3');
}
.module-title-name {
@include color('e');
}
}
}
.index-categary-head {
@include color('e');
}
.item-link-a {
@include color('a');
}
.nav-tool {
@include color('e');
div {
@include background-color('3');
img {
@include fill('e');
}
svg {
circle {
@include stroke('e');
}
path {
@include fill('e');
}
}
a {
@include color('e');
div {
@include color('e');
}
}
}
}
#{contains('mediainfo_mediaToolbar')} {
#{contains('mediainfo_btnHome')} {
@include theme-background-color(20);
svg path {
@include theme-fill();
}
}
#{contains('follow_btnFollow')} {
@include theme-background-color();
}
#{contains('follow_followed')} {
@include background-color('4');
}
#{contains('follow_optionsList')} {
@include background-color('3');
@include border-color('4');
}
}
#{contains('sponsor_sponsor_module')} {
@include set-color('border-bottom-color', '4');
}
#{contains('rankList_sp_msg')} {
@include border-color('4');
@include color('e');
}
#{contains('sponsor_sponsor_count')} span {
@include color('e');
}
#{contains('amountChoice_sp_pay_index')} {
#{contains('payHeader_sponsor_pay_header')} {
@include color('a');
@include background-color('2');
@include set-color('border-bottom-color', '4');
}
#{contains('amountChoice_sponsor_pay_body')} {
span,
div {
@include color('e');
input {
@include background-color('4');
@include color('e');
@include border-color('2');
&:focus {
@include theme-border-color();
}
}
a {
@include theme-color();
}
}
@include background-color('3');
}
}
#{contains('mediainfo_mediaInfo')},
#{contains('recommend_wrap')} {
@include set-color('border-top-color', '4');
}
#{contains('mediainfo_mediaRight')} {
#{contains('mediainfo_media_desc_section')}
#{contains('mediainfo_display_area')}
#{contains('mediainfo_ellipsis')} {
background: linear-gradient(90deg, hsla(0, 0%, 100%, 0), #222 20%, #222);
@include theme-color();
}
#{contains('mediainfo_media_desc')} i {
@include theme-color();
@include background-color();
}
}
#{contains('SectionSelector_SectionSelector')} {
#{contains('SectionSelector_expand')} {
background: linear-gradient(270deg, #444444 46.21%, hsla(210, 8%, 95%, 0));
}
}
#{contains('PlayingIcon_playIcon')} svg path {
@include theme-stroke();
}
#{contains('Share_boxTop')} {
@include background-color('3');
}
#{contains('Share_boxBottom')} {
@include background-color('4');
}
#{contains('WatchInfo_watch_more')} {
@include background-color('3');
@include border-color('4');
#{contains('WatchInfo_title')} {
@include color('e');
}
}
#{contains('Phone_mobile_more')} {
@include background-color('3');
@include border-color('4');
#{contains('Phone_top_wrapper')} {
canvas {
border: 5px solid #ffffff;
}
#{contains('Phone_video_title')} {
@include color('e');
}
}
#{contains('Phone_mobile_tip')} {
@include color('a');
a {
@include theme-color();
}
}
}
#{contains('short_short_review_masker')} {
#{contains('short_short_review_content')} {
@include background-color('2');
#{contains('header_review_edit_media_info')} {
h4,
p {
@include color('e');
}
}
#{contains('short_review_body_wrap')} {
@include border-color('4');
}
#{contains('short_review_footer')} {
#{contains('short_mr_long_review')} {
@include theme-color();
}
#{contains('short_active')} {
@include theme-background-color();
}
}
}
}

View File

@ -1,7 +1,8 @@
#recom_module,
#reco_list,
.bilibili-player-ending-panel-box-videos,
.r-con .rcmd-list {
.r-con .rcmd-list,
.plp-r [class*="recommend_wrap"] {
display: none !important;
}
.bilibili-player-ending-panel-box-functions .bilibili-player-upinfo-spans {

View File

@ -87,11 +87,14 @@ $prefix: 'simplifyLiveroom-switch';
width: auto !important;
@include absolute-center();
.right-ctnr,
.right-ctnr > :not(.popular-and-hot-rank),
.left-ctnr > :nth-child(n + 3) {
display: none !important;
}
}
.popular-and-hot-rank {
width: 0 !important;
}
.room-owner-username {
max-width: 150px !important;
}

View File

@ -36,7 +36,7 @@ const entry = async () => {
})
}
processItems([comment, ...comment.replies])
comment.onRepliesUpdate = replies => processItems(replies)
comment.addEventListener('repliesUpdate', e => processItems(e.detail))
}
forEachCommentItem({
added: addCopyLinkButton,

View File

@ -288,7 +288,7 @@ export const decodeDanmakuView = decode('DmWebViewReply')
// 这里为了兼容 pakku, 只能用 fetch https://github.com/xmcp/pakku.js/issues/153
const fetchBlob = async (url: string) => {
const response = await fetch(url)
const response = await fetch(url, { mode: 'cors', credentials: 'include' })
return response.blob()
}
export const getDanmakuView = async (aid: string | number, cid: string | number) => {

View File

@ -52,8 +52,12 @@ export class JsonDanmaku {
new Array(total).fill(0).map(async (_, index) => {
try {
const result = await getDanmakuSegment(this.aid, this.cid, index)
console.log(`received blob for segment ${index + 1}`, result)
return result.elems ?? []
const elements: any[] = result.elems ?? []
console.log(
`received blob for segment ${index + 1}, count = ${elements.length}, result =`,
result,
)
return elements
} catch (error) {
logError(error)
throw error

View File

@ -1,8 +1,9 @@
import { defineComponentMetadata } from '@/components/define'
import { playerAgent } from '@/components/video/player-agent'
import { videoChange } from '@/core/observer'
import { childListSubtree, videoChange } from '@/core/observer'
import { select } from '@/core/spin-query'
import { playerReady } from '@/core/utils'
import { useScopedConsole } from '@/core/utils/log'
import { videoUrls } from '@/core/utils/urls'
export const component = defineComponentMetadata({
@ -12,6 +13,7 @@ export const component = defineComponentMetadata({
tags: [componentsTags.video],
urlInclude: videoUrls,
entry: async () => {
const console = useScopedConsole('传统连播模式')
const autoPlayControls = {
// 命中时应该打开连播: 传统分 P, 合集
enable: [
@ -65,5 +67,11 @@ export const component = defineComponentMetadata({
const video = (await playerAgent.query.video.element()) as HTMLVideoElement
video?.addEventListener('play', checkPlayMode, { once: true })
})
const rightPanelContainer = await select('.right-container-inner')
if (!rightPanelContainer) {
console.warn('未找到 rightPanelContainer')
return
}
childListSubtree(rightPanelContainer, () => checkPlayMode())
},
})

View File

@ -0,0 +1 @@
启用下载字幕支持, 在视频页面中可从功能面板里下载字幕. (AI 生成的不可下载)

View File

@ -9,9 +9,6 @@ import { getBlobByType, SubtitleDownloadType } from './utils'
export const component = defineComponentMetadata({
name: 'downloadSubtitle',
displayName: '下载字幕',
description: {
'zh-CN': '启用下载字幕支持, 在视频页面中可从功能面板里下载字幕.',
},
tags: [componentsTags.video],
entry: none,
urlInclude: videoAndBangumiUrls,

View File

@ -1,8 +1,19 @@
import { getJson } from '@/core/ajax'
import { bilibiliApi, getJson, getJsonWithCredentials } from '@/core/ajax'
import { Toast } from '@/core/toast'
import { getFriendlyTitle } from '@/core/utils/title'
import { SubtitleConverterConfig } from '../subtitle-converter'
export interface SubtitleInfo {
id: number
id_str: string
lan: string
lan_doc: string
is_lock: boolean
subtitle_url: string
type: number
ai_type: number
ai_status: number
}
export type SubtitleDownloadType = 'json' | 'ass'
export const getSubtitleConfig = async (): Promise<[SubtitleConverterConfig, string]> => {
const { SubtitleConverter, SubtitleSize, SubtitleLocation } = await import(
@ -64,11 +75,10 @@ export const getSubtitleConfig = async (): Promise<[SubtitleConverterConfig, str
return [config, language]
}
export const getSubtitleList = async (aid: string, cid: string | number) => {
const { VideoInfo } = await import('@/components/video/video-info')
const info = new VideoInfo(aid)
info.cid = typeof cid === 'string' ? parseInt(cid) : cid
await info.fetchInfo()
return info.subtitles
const data = await bilibiliApi(
getJsonWithCredentials(`https://api.bilibili.com/x/player/wbi/v2?aid=${aid}&cid=${cid}`),
)
return lodash.get(data, 'subtitle.subtitles', []) as SubtitleInfo[]
}
export const getBlobByType = async (
type: SubtitleDownloadType,
@ -88,8 +98,8 @@ export const getBlobByType = async (
return null
}
const [config, language] = await getSubtitleConfig()
const subtitle = subtitles.find(s => s.languageCode === language) || subtitles[0]
const json = await getJson(subtitle.url)
const subtitle = subtitles.find(s => s.lan === language) || subtitles[0]
const json = await getJson(subtitle.subtitle_url)
const rawData = json.body
switch (type) {
case 'ass': {
@ -102,7 +112,7 @@ export const getBlobByType = async (
}
default:
case 'json': {
return new Blob([JSON.stringify(rawData)], {
return new Blob([JSON.stringify(rawData, undefined, 2)], {
type: 'text/json',
})
}

View File

@ -23,7 +23,7 @@ const entry = async () => {
forEachCommentItem({
added: item => {
const { element } = item
item.onRepliesUpdate = replies => replies.forEach(r => injectButton(r.element))
item.addEventListener('repliesUpdate', e => e.detail.forEach(r => injectButton(r.element)))
injectButton(element)
},
})

View File

@ -1,5 +1,5 @@
{
"version": "2.7.4",
"version": "2.7.5",
"author": "Grant Howard, Coulomb-G",
"copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)",
"license": "MIT",

View File

@ -7,8 +7,8 @@
@click.self="performAction($event)"
@keydown.enter.prevent.stop="performAction($event)"
@keydown.shift.delete.prevent.stop="performDelete($event)"
@keydown.up.prevent.stop="$emit('previous-item', $event)"
@keydown.down.prevent.stop="$emit('next-item', $event)"
@keydown.up.prevent.stop="$emit('previous-item', $event.currentTarget)"
@keydown.down.prevent.stop="$emit('next-item', $event.currentTarget)"
>
<div class="suggest-item-content">
<div v-if="action.icon" class="suggest-item-icon" @click="performAction($event)">
@ -54,15 +54,17 @@ export default Vue.extend({
},
methods: {
async performAction(event: KeyboardEvent | MouseEvent) {
const { currentTarget } = event
await this.action.action()
this.$emit('action', event)
this.$emit('action', currentTarget)
},
async performDelete(event: KeyboardEvent | MouseEvent) {
const { currentTarget } = event
if (!this.action.deleteAction) {
return
}
await this.action.deleteAction()
this.$emit('delete-item', event)
this.$emit('delete-item', currentTarget)
},
},
})

View File

@ -221,24 +221,24 @@ export default Vue.extend({
this.$refs.list.querySelector('.suggest-item').focus()
e.preventDefault()
},
previousItem(e: KeyboardEvent, index: number) {
previousItem(element: HTMLElement, index: number) {
if (index === 0) {
this.focus()
} else {
;((e.currentTarget as HTMLElement).previousElementSibling as HTMLElement).focus()
;(element.previousElementSibling as HTMLElement).focus()
}
},
nextItem(e: KeyboardEvent, index: number) {
nextItem(element: HTMLElement, index: number) {
const lastItemIndex = this.actions.length - 1
if (index !== lastItemIndex) {
;((e.currentTarget as HTMLElement).nextElementSibling as HTMLElement).focus()
;(element.nextElementSibling as HTMLElement).focus()
} else {
this.focus()
}
},
search,
onDeleteItem(e: Event, index: number) {
this.previousItem(e, index)
onDeleteItem(element: HTMLElement, index: number) {
this.previousItem(element, index)
this.getActions()
},
onClearHistory() {

View File

@ -3,7 +3,7 @@ import { allMutations, childList, childListSubtree } from '@/core/observer'
import { contentLoaded } from '@/core/life-cycle'
/** 表示一条评论回复 */
export interface CommentReplyItem {
export class CommentReplyItem extends EventTarget {
/** 对应元素 */
element: HTMLElement
/** 评论ID */
@ -20,15 +20,62 @@ export interface CommentReplyItem {
time?: number
/** 点赞数 */
likes: number
constructor(initParams: Omit<CommentReplyItem, keyof EventTarget>) {
super()
this.element = initParams.element
this.id = initParams.id
this.userId = initParams.userId
this.userName = initParams.userName
this.content = initParams.content
this.timeText = initParams.timeText
this.time = initParams.time
this.likes = initParams.likes
}
}
/** 评论更新的事件类型 */
export const RepliesUpdateEventType = 'repliesUpdate'
/** 评论更新的事件回调类型 */
export type RepliesUpdateEventCallback = (event: CustomEvent<CommentReplyItem[]>) => void
const createRepliesUpdateEvent = (replies: CommentReplyItem[]) => {
return new CustomEvent(RepliesUpdateEventType, {
detail: replies,
})
}
/** 表示一条评论 */
export interface CommentItem extends CommentReplyItem {
export class CommentItem extends CommentReplyItem {
/** 评论图片 */
pictures?: string[]
/** 回复 */
replies: CommentReplyItem[]
/** 回复有更新时调用此函数 */
onRepliesUpdate?: (replies: CommentReplyItem[]) => void
constructor(initParams: Omit<CommentItem, keyof EventTarget | 'dispatchRepliesUpdate'>) {
super(initParams)
this.pictures = initParams.pictures
this.replies = initParams.replies
}
addEventListener(
type: typeof RepliesUpdateEventType,
callback: RepliesUpdateEventCallback | null,
options?: EventListenerOptions | boolean,
) {
super.addEventListener(type, callback, options)
}
removeEventListener(
type: typeof RepliesUpdateEventType,
callback: RepliesUpdateEventCallback | null,
options?: EventListenerOptions | boolean,
) {
super.removeEventListener(type, callback, options)
}
/** 触发评论更新事件 */
dispatchRepliesUpdate(replies: CommentReplyItem[]) {
return super.dispatchEvent(createRepliesUpdateEvent(replies))
}
}
export type CommentItemCallback = (item: CommentItem) => void
/** 表示一个评论区 */
@ -84,7 +131,7 @@ const parseCommentItemV2 = (element: HTMLElement) => {
return []
}
return vueData.replies.map((r: any): CommentReplyItem => {
return {
return new CommentReplyItem({
id: r.rpid_str,
element: getReplyItemElement(element, r.rpid_str),
userId: r.member.mid,
@ -92,10 +139,10 @@ const parseCommentItemV2 = (element: HTMLElement) => {
content: r.content.message,
time: r.ctime * 1000,
likes: r.like,
}
})
})
}
const item: CommentItem = {
const item = new CommentItem({
id: vueData.rpid_str,
element,
userId: vueData.member.mid,
@ -107,11 +154,14 @@ const parseCommentItemV2 = (element: HTMLElement) => {
return img.img_src
}),
replies: parseReplies(),
}
})
if (item.replies.length < vueData.rcount) {
const replyBox = dq(element, '.sub-reply-list')
childList(replyBox, () => {
childList(replyBox, records => {
item.replies = parseReplies()
if (records.length !== 0) {
item.dispatchRepliesUpdate(item.replies)
}
})
}
return item
@ -126,7 +176,7 @@ const parseCommentItem = (element: HTMLElement) => {
const parseReplyItem = (replyElement: HTMLElement) => {
const replyFace = replyElement.querySelector('.reply-face') as HTMLElement
const replyUser = replyElement.querySelector('.reply-con .user .name') as HTMLElement
return {
return new CommentReplyItem({
id: replyElement.getAttribute('data-id'),
element: replyElement,
userId: replyFace.getAttribute('data-usercard-mid'),
@ -134,9 +184,9 @@ const parseCommentItem = (element: HTMLElement) => {
content: replyElement.querySelector('.text-con').textContent,
timeText: replyElement.querySelector('.info .time, .info .time-location').textContent,
likes: parseInt(replyElement.querySelector('.info .like span').textContent),
}
})
}
const item: CommentItem = {
const item = new CommentItem({
id: element.getAttribute('data-id'),
element,
userId: user.getAttribute('data-usercard-mid'),
@ -145,13 +195,13 @@ const parseCommentItem = (element: HTMLElement) => {
timeText: element.querySelector('.con .info .time, .info .time-location').textContent,
likes: parseInt(element.querySelector('.con .like span').textContent),
replies: [],
}
})
if (dq(element, '.reply-box .view-more')) {
const replyBox = dq(element, '.reply-box') as HTMLElement
childList(replyBox, records => {
item.replies = dqa(element, '.reply-box .reply-item').map(parseReplyItem)
if (records.length !== 0) {
item.onRepliesUpdate?.(item.replies)
item.dispatchRepliesUpdate(item.replies)
}
})
} else {

View File

@ -1,6 +1,7 @@
import { attributes } from '@/core/observer'
import { select } from '@/core/spin-query'
import { playerReady, preventEvent } from '@/core/utils'
import { createPlayerModeChangeEvent, PlayerMode } from './events'
const playerModePolyfill = async () => {
await playerReady()
@ -9,16 +10,29 @@ const playerModePolyfill = async () => {
console.warn('[bpx player polyfill] bpxContainer not found')
return
}
let lastScreen = PlayerMode.Normal
attributes(bpxContainer, () => {
const dataScreen = bpxContainer.getAttribute('data-screen')
const dataScreen = bpxContainer.getAttribute('data-screen') as PlayerMode
const prefix = 'player-mode-'
const enumList = ['normal', 'wide', 'web', 'full'].map(it => `${prefix}${it}`)
const enumList = [
PlayerMode.Normal,
PlayerMode.WideScreen,
PlayerMode.WebFullscreen,
PlayerMode.Fullscreen,
].map(it => `${prefix}${it}`)
// clear all class
document.body.classList.remove(...enumList)
// add class
document.body.classList.add(dataScreen !== 'normal' ? `${prefix}${dataScreen}` : '')
if (dataScreen !== PlayerMode.Normal) {
document.body.classList.add(`${prefix}${dataScreen}`)
}
if (dataScreen !== lastScreen) {
window.dispatchEvent(createPlayerModeChangeEvent(dataScreen))
lastScreen = dataScreen
}
})
}
const idPolyfill = async () => {

View File

@ -0,0 +1,11 @@
export enum PlayerMode {
Normal = 'normal',
WideScreen = 'wide',
WebFullscreen = 'web',
Fullscreen = 'full',
}
export const createPlayerModeChangeEvent = (mode: PlayerMode) =>
new CustomEvent('playerModeChange', {
detail: { mode, bubbles: true, cancelable: true },
})

View File

@ -21,12 +21,6 @@ export class VideoInfo {
title: string
pageNumber: number
}[]
subtitles: {
id: number
languageCode: string
language: string
url: string
}[]
constructor(id: string, bvid = false) {
if (bvid) {
@ -70,15 +64,20 @@ export class VideoInfo {
title: it.part,
pageNumber: it.page,
}))
this.subtitles =
data.subtitle?.list?.map((it: any) => ({
id: it.id,
languageCode: it.lan,
language: it.lan_doc,
url: it.subtitle_url.replace('http:', 'https:'),
})) ?? []
return this
}
/** @deprecated */
// eslint-disable-next-line class-methods-use-this
get subtitles(): {
id: number
languageCode: string
language: string
url: string
}[] {
console.warn('VideoInfo.subtitles is deprecated')
return []
}
}
export class BangumiInfo {
ep: number

View File

@ -133,9 +133,9 @@ export default Vue.extend({
const eventTypes = ['mousedown', 'touchstart']
eventTypes.forEach(type => {
if (this.open) {
document.documentElement.addEventListener(type, this.openHandler)
document.documentElement.addEventListener(type, e => this.openHandler(e))
} else {
document.documentElement.removeEventListener(type, this.openHandler)
document.documentElement.removeEventListener(type, e => this.openHandler(e))
}
})
}

View File

@ -1,7 +1,7 @@
import { CdnConfig } from './types'
const owner = 'the1812'
const host = 'fastly.jsdelivr.net'
const host = 'cdn.jsdelivr.net'
export const jsDelivr: CdnConfig = {
name: 'jsDelivr',
owner,