mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge branch 'preview-fixes'
This commit is contained in:
commit
7797af6344
@ -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
|
||||
},
|
||||
{
|
||||
|
||||
101
CHANGELOG.md
101
CHANGELOG.md
@ -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`
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@
|
||||
不兼容.
|
||||
|
||||
### [AdGuard](https://adguard.com/zh_cn/adguard-windows/overview.html)
|
||||
未测试.
|
||||
不兼容.
|
||||
|
||||
## 浏览器
|
||||
|
||||
|
||||
1046
doc/donate.md
1046
doc/donate.md
File diff suppressed because it is too large
Load Diff
@ -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
@ -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"
|
||||
},
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -860,6 +860,9 @@
|
||||
}
|
||||
.bili-comment.browser-pc {
|
||||
@include background-color();
|
||||
.comment-container {
|
||||
@include background-color();
|
||||
}
|
||||
}
|
||||
.recommended-container,
|
||||
.recommended-container_floor-aside {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
},
|
||||
})
|
||||
|
||||
1
registry/lib/components/video/subtitle/download/index.md
Normal file
1
registry/lib/components/video/subtitle/download/index.md
Normal file
@ -0,0 +1 @@
|
||||
启用下载字幕支持, 在视频页面中可从功能面板里下载字幕. (AI 生成的不可下载)
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
},
|
||||
})
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
11
src/components/video/player-adaptor/events.ts
Normal file
11
src/components/video/player-adaptor/events.ts
Normal 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 },
|
||||
})
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user