mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge branch 'preview-features' into preview
This commit is contained in:
commit
bb5ab37698
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -51,6 +51,7 @@
|
||||
"Fullscreen",
|
||||
"githubusercontent",
|
||||
"Greasemonkey",
|
||||
"guochuang",
|
||||
"haruna",
|
||||
"hdslb",
|
||||
"HEVC",
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,6 +1,41 @@
|
||||
<!-- spell-checker: disable -->
|
||||
# 更新日志
|
||||
|
||||
## v2.9.6-preview
|
||||
`2024-12-28`
|
||||
|
||||
包含 [v2.9.6](https://github.com/the1812/Bilibili-Evolved/releases/tag/v2.9.6) 的所有更新内容.
|
||||
|
||||
- 新增插件 `自定义顶栏 - 版权内容`. (#5045)
|
||||
> 为自定义顶栏扩充版权内容相关的快速入口, 包括国创 / 电影 / 电视剧 /综艺 / 纪录片
|
||||
|
||||
- 视频截图在还有截图未处理 (保存 / 丢弃) 时, 离开页面增加二次确认. (#5066)
|
||||
|
||||
## v2.9.6
|
||||
`2024-12-28`
|
||||
|
||||
✨新增
|
||||
- `自定义顶栏` 更新:
|
||||
- (来自 v2.9.5-preview) 历史支持手动刷新. (#4948)
|
||||
- 全局固定适配话题页. (#5039)
|
||||
- 新歌热榜 URL 更新. (#5056)
|
||||
- `订阅` 重命名为 `番剧`, 功能不变. (#4777)
|
||||
- 增加 VLOG, 搞笑, 综艺, 单机游戏入口. (#1511)
|
||||
- `传统连播模式` 支持番剧页面. (#5067)
|
||||
- `下载视频` 增加选项 `DASH 回退编码`, 默认回退到 AVC, 可以避免 HEVC 回退到 AV1 的情况. (#5080)
|
||||
- 新增功能 `隐藏首页轮播图`. (从上一版本废弃的 `简化首页` 中提取) (#5038)
|
||||
- `夜间模式` 优化对动态和新版首页的适配. (#5075)
|
||||
|
||||
🐛修复
|
||||
- 修复 `隐藏头像框` 对部分角标删除不完全. (#5040)
|
||||
- 修复 `自定义顶栏` 删除搜索历史时失焦. (#4732)
|
||||
- 修复 `自定义顶栏` 未登录时仍能通过功能面板进行排序. (#5051)
|
||||
- 修复 `自定义顶栏` 收藏夹的音频链接错误. (#5069)
|
||||
- 修复在搜索页的样式冲突. (#5073)
|
||||
- 修复 `直播首页静音` 在关闭时仍会导致首页静音. (#5045)
|
||||
- 修复 `禁止滚轮调音量` 在 Firefox 中不生效. (#5047)
|
||||
- 回退 `直播信息扩充` 的 API 变更. (#4964)
|
||||
|
||||
## v2.9.5-preview
|
||||
`2024-11-12`
|
||||
|
||||
|
||||
@ -30,6 +30,12 @@ https://afdian.com/a/the1812?tab=sponsor
|
||||
|
||||
| 时间 | 用户名 | 单号后4位 | 金额 |
|
||||
| ------------------- | --------------------- | --------- | ------- |
|
||||
| 2024.12.27 11:01:16 | *o | 2835 | ¥5.00 |
|
||||
| 2024.12.20 18:05:09 | *言 | 6840 | ¥3.00 |
|
||||
| 2024.12.17 18:30:53 | x*x | 3632 | ¥10.00 |
|
||||
| 2024.12.10 12:20:10 | *吗 | 8875 | ¥5.00 |
|
||||
| 2024.12.06 14:33:35 | 匿名 | 8998 | ¥5.00 |
|
||||
| 2024.11.17 05:46:36 | *树 | 0442 | ¥20.00 |
|
||||
| 2024.10.24 19:59:56 | 匿名 | 5126 | ¥20.00 |
|
||||
| 2024.10.24 03:07:40 | S*a | 8703 | ¥20.00 |
|
||||
| 2024.10.11 11:19:52 | 匿名 | 8738 | ¥100.00 |
|
||||
|
||||
@ -295,6 +295,14 @@
|
||||
"fullRelativePath": "../../registry/dist/components/style/hide/banner.js",
|
||||
"fullAbsolutePath": "registry/dist/components/style/hide/banner.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "hideHomeCarousel",
|
||||
"displayName": "隐藏首页轮播图",
|
||||
"description": "隐藏首页的轮播图区域.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/components/style/hide/home-carousel.js",
|
||||
"fullAbsolutePath": "registry/dist/components/style/hide/home-carousel.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "hideTrendingSearch",
|
||||
@ -1048,6 +1056,14 @@
|
||||
"fullRelativePath": "../../registry/dist/plugins/style/custom-navbar-dark-mode.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/style/custom-navbar-dark-mode.js"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "customNavbar.items.pgc",
|
||||
"displayName": "自定义顶栏 - 版权内容",
|
||||
"description": "为自定义顶栏扩充版权内容相关的快速入口, 包括国创 / 电影 / 电视剧 /综艺 / 纪录片",
|
||||
"fullRelativePath": "../../registry/dist/plugins/style/custom-navbar-pgc.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/style/custom-navbar-pgc.js"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "keymap.actions.darkMode",
|
||||
@ -1116,7 +1132,7 @@
|
||||
"type": "plugin",
|
||||
"name": "downloadVideo.outputs.idm",
|
||||
"displayName": "下载视频 - IDM 输出支持",
|
||||
"description": "为下载视频增加 IDM 输出支持,建议配合 [ef2.exe](https://github.com/MotooriKashin/ef2) 以简化操作、保留文件名.",
|
||||
"description": "为下载视频增加 IDM 输出支持.",
|
||||
"fullRelativePath": "../../registry/dist/plugins/video/download/idm-output.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/video/download/idm-output.js"
|
||||
},
|
||||
|
||||
@ -387,6 +387,15 @@ by [@Tinhone](https://github.com/Tinhone)
|
||||
|
||||
隐藏首页顶部横幅.
|
||||
|
||||
### [隐藏首页轮播图](../../registry/dist/components/style/hide/home-carousel.js)
|
||||
`hideHomeCarousel`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/hide/home-carousel.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/hide/home-carousel.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/hide/home-carousel.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/hide/home-carousel.js)
|
||||
|
||||
隐藏首页的轮播图区域.
|
||||
|
||||
### [隐藏热搜](../../registry/dist/components/style/hide/trending-search.js)
|
||||
`hideTrendingSearch`
|
||||
|
||||
@ -1457,6 +1466,15 @@ by FoundTheWOUT
|
||||
|
||||
为自定义顶栏添加一个夜间模式开关, 方便快速切换夜间模式.
|
||||
|
||||
### [自定义顶栏 - 版权内容](../../registry/dist/plugins/style/custom-navbar-pgc.js)
|
||||
`customNavbar.items.pgc`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/plugins/style/custom-navbar-pgc.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/plugins/style/custom-navbar-pgc.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/style/custom-navbar-pgc.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/style/custom-navbar-pgc.js)
|
||||
|
||||
为自定义顶栏扩充版权内容相关的快速入口, 包括国创 / 电影 / 电视剧 /综艺 / 纪录片
|
||||
|
||||
### [快捷键扩展 - 夜间模式](../../registry/dist/plugins/utils/keymap-dark-mode.js)
|
||||
`keymap.actions.darkMode`
|
||||
|
||||
@ -1536,7 +1554,7 @@ by FoundTheWOUT
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/video/download/idm-output.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/video/download/idm-output.js)
|
||||
|
||||
为下载视频增加 IDM 输出支持,建议配合 [ef2.exe](https://github.com/MotooriKashin/ef2) 以简化操作、保留文件名.
|
||||
为下载视频增加 IDM 输出支持.
|
||||
|
||||
### [下载视频 - 手动输入](../../registry/dist/plugins/video/download/manual-input.js)
|
||||
`downloadVideo.inputs.manual`
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { VIcon, TextBox, DpiImage, VEmpty, VLoading } from '@/ui'
|
||||
import { getJsonWithCredentials } from '@/core/ajax'
|
||||
import { getJsonWithCredentials, responsiveGetPages } from '@/core/ajax'
|
||||
|
||||
const decodeTitle = (title: string) => {
|
||||
const textArea = document.createElement('textarea')
|
||||
@ -109,26 +109,65 @@ export default Vue.extend({
|
||||
try {
|
||||
this.items = []
|
||||
this.loaded = false
|
||||
const url = `https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?hit_ab=true&_=${Date.now()}`
|
||||
const portalList = await getJsonWithCredentials(url)
|
||||
this.items = portalList.data.rooms.map(item => {
|
||||
const { room_id, face, title, uname, uid } = item
|
||||
return {
|
||||
cover: face,
|
||||
face,
|
||||
uname,
|
||||
title,
|
||||
roomid: room_id,
|
||||
pic: '', // portal接口没有
|
||||
online: 0, // portal接口没有
|
||||
uid,
|
||||
link: `https://live.bilibili.com/${room_id}`,
|
||||
}
|
||||
const [, promise] = responsiveGetPages<LiveInfo>({
|
||||
api: page =>
|
||||
getJsonWithCredentials(
|
||||
`https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?page=${page}`,
|
||||
),
|
||||
getList: json =>
|
||||
lodash.get(json, 'data.list', []).map(item => {
|
||||
const { face, uname, title, room_id, cover_from_user, online, uid, link } = item
|
||||
return {
|
||||
cover: face,
|
||||
face,
|
||||
uname,
|
||||
title,
|
||||
roomid: room_id,
|
||||
pic: cover_from_user,
|
||||
online,
|
||||
uid,
|
||||
link,
|
||||
}
|
||||
}),
|
||||
getTotal: json => lodash.get(json, 'data.count', 0),
|
||||
})
|
||||
|
||||
const [allItems, recommendItems] = await Promise.all([promise, this.fetchRecommendItems()])
|
||||
this.items = this.sortByRecommend(allItems, recommendItems)
|
||||
} finally {
|
||||
this.loaded = true
|
||||
}
|
||||
},
|
||||
|
||||
async fetchRecommendItems(): Promise<LiveInfo[]> {
|
||||
// 动态portal接口会获取推荐的top30直播。同时这个接口不会忽略悄悄关注的up的直播。
|
||||
const portalList = await getJsonWithCredentials(
|
||||
'https://api.bilibili.com/x/polymer/web-dynamic/v1/portal',
|
||||
)
|
||||
const recommendLiveItems = portalList.data.live_users.items.map(item => {
|
||||
const { jump_url, room_id, face, title, uname, mid } = item
|
||||
return {
|
||||
cover: face,
|
||||
face,
|
||||
uname,
|
||||
title,
|
||||
roomid: room_id,
|
||||
pic: '', // portal接口没有
|
||||
online: 0, // portal接口没有
|
||||
uid: mid,
|
||||
link: jump_url,
|
||||
}
|
||||
})
|
||||
return recommendLiveItems
|
||||
},
|
||||
|
||||
sortByRecommend(feedItems: LiveInfo[], recommendItems: LiveInfo[]) {
|
||||
// recommendItems里的pic和online为默认值,但是ui也没用到。
|
||||
// 所以方便起见,直接拼接没出现在recommendItems里的item
|
||||
const recommendRoomIds = recommendItems.map(item => item.roomid)
|
||||
const feedConcatItems = feedItems.filter(item => !recommendRoomIds.includes(item.roomid))
|
||||
return lodash.concat(recommendItems, feedConcatItems)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -37,6 +37,10 @@ export const component = defineComponentMetadata({
|
||||
'zh-CN': '提前执行代码以尽快静音.',
|
||||
},
|
||||
async setup() {
|
||||
const { isComponentEnabled } = await import('@/core/settings')
|
||||
if (!isComponentEnabled('liveHomeMute')) {
|
||||
return
|
||||
}
|
||||
const { matchUrlPattern } = await import('@/core/utils')
|
||||
if (!matchUrlPattern(liveHome)) {
|
||||
return
|
||||
|
||||
@ -34,6 +34,7 @@ export const entry: ComponentEntry = async ({ metadata: { name } }) => {
|
||||
'https://www.bilibili.com/read',
|
||||
'https://www.bilibili.com/opus',
|
||||
'https://www.bilibili.com/account/history',
|
||||
'https://www.bilibili.com/v/topic/detail',
|
||||
]
|
||||
if (!globalFixedExclude.some(p => matchUrlPattern(p))) {
|
||||
addComponentListener(
|
||||
|
||||
@ -21,11 +21,7 @@
|
||||
<VEmpty v-else-if="!loading && !canLoadMore && filteredCards.length === 0"></VEmpty>
|
||||
<transition-group v-else name="cards" tag="div" class="cards">
|
||||
<div v-for="card of filteredCards" :key="card.id" class="favorite-card">
|
||||
<a
|
||||
class="favorites-cover-container"
|
||||
target="_blank"
|
||||
:href="'https://www.bilibili.com/video/' + card.bvid"
|
||||
>
|
||||
<a class="favorites-cover-container" target="_blank" :href="getItemPlayLink(card)">
|
||||
<DpiImage
|
||||
class="cover"
|
||||
:src="card.coverUrl"
|
||||
@ -34,13 +30,9 @@
|
||||
<div class="floating duration">{{ card.durationText }}</div>
|
||||
<div class="floating favorite-time">{{ card.favoriteTime }}</div>
|
||||
</a>
|
||||
<a
|
||||
class="title"
|
||||
target="_blank"
|
||||
:href="'https://www.bilibili.com/video/' + card.bvid"
|
||||
:title="card.title"
|
||||
>{{ card.title }}</a
|
||||
>
|
||||
<a class="title" target="_blank" :href="getItemPlayLink(card)" :title="card.title">{{
|
||||
card.title
|
||||
}}</a>
|
||||
<a
|
||||
v-if="card.upID"
|
||||
class="up"
|
||||
@ -82,7 +74,8 @@ https://api.bilibili.com/x/v3/fav/resource/list?media_id=media_id&pn=1&ps=20&key
|
||||
*/
|
||||
|
||||
const navbarOptions = getComponentSettings('customNavbar').options
|
||||
interface FavoritesItemInfo extends VideoCard {
|
||||
interface FavoritesItemInfo extends Omit<VideoCard, 'type'> {
|
||||
type: number
|
||||
favoriteTimestamp: number
|
||||
favoriteTime: string
|
||||
}
|
||||
@ -94,6 +87,7 @@ const favoriteItemFilter = (item: any): boolean => {
|
||||
return item.attr !== 9 && item.attr !== 1 // 过滤掉已失效视频
|
||||
}
|
||||
const favoriteItemMapper = (item: any): FavoritesItemInfo => ({
|
||||
type: item.type,
|
||||
id: item.id,
|
||||
aid: item.id,
|
||||
bvid: item.bvid,
|
||||
@ -269,6 +263,17 @@ export default Vue.extend({
|
||||
this.loadNextPage()
|
||||
}
|
||||
},
|
||||
getItemPlayLink(item: FavoritesItemInfo) {
|
||||
switch (item.type) {
|
||||
default:
|
||||
case 2: {
|
||||
return `https://www.bilibili.com/video/${item.bvid}`
|
||||
}
|
||||
case 12: {
|
||||
return `https://www.bilibili.com/audio/au${item.id}`
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -6,6 +6,11 @@
|
||||
<TextBox v-model="search" placeholder="搜索" linear></TextBox>
|
||||
</div>
|
||||
<div class="operations">
|
||||
<div class="operation">
|
||||
<VButton title="刷新" round @click="reloadHistoryItems()">
|
||||
<VIcon icon="mdi-refresh" :size="16"></VIcon>
|
||||
</VButton>
|
||||
</div>
|
||||
<div class="operation" @click="toggleHistoryPause">
|
||||
<VButton v-if="!paused" title="暂停记录历史" round>
|
||||
<VIcon icon="mdi-pause" :size="14"></VIcon>
|
||||
|
||||
@ -1,17 +1,11 @@
|
||||
<template>
|
||||
<div class="custom-navbar-extra-options">
|
||||
<VButton
|
||||
v-if="login"
|
||||
ref="button"
|
||||
@mouseover="loadNavbarSettings()"
|
||||
@click="toggleNavbarSettings()"
|
||||
>
|
||||
<VButton ref="button" @mouseover="loadNavbarSettings()" @click="toggleNavbarSettings()">
|
||||
布局设置<VIcon icon="right-arrow" :size="16"></VIcon>
|
||||
</VButton>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { getUID } from '@/core/utils'
|
||||
import { VIcon, VButton } from '@/ui'
|
||||
import { setTriggerElement, loadNavbarSettings, toggleNavbarSettings } from './vm'
|
||||
|
||||
@ -20,11 +14,6 @@ export default Vue.extend({
|
||||
VIcon,
|
||||
VButton,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
login: Boolean(getUID()),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadNavbarSettings() {
|
||||
const isFirstLoad = await loadNavbarSettings()
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
<div class="padding-value">{{ padding.toFixed(1) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-settings-section">
|
||||
<div v-if="isLogin" class="navbar-settings-section">
|
||||
<div class="navbar-settings-section-title">元素呈现</div>
|
||||
<div class="navbar-settings-section-description">
|
||||
按住并拖动可以调整顺序, 点击眼睛图标可以切换隐藏/显示.
|
||||
@ -71,7 +71,7 @@
|
||||
import { SortableEvent } from 'sortablejs'
|
||||
import { VPopup, VIcon, VSlider, VLoading } from '@/ui'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { dqa } from '@/core/utils'
|
||||
import { dqa, getUID } from '@/core/utils'
|
||||
import { SortableJSLibrary } from '@/core/runtime-library'
|
||||
import { getData } from '@/plugins/data'
|
||||
import { CustomNavbarItem, CustomNavbarRenderedItems } from '../custom-navbar-item'
|
||||
@ -103,6 +103,7 @@ export default Vue.extend({
|
||||
rendered,
|
||||
hidden: navbarOptions.hidden,
|
||||
loaded: false,
|
||||
isLogin: Boolean(getUID()),
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@ -123,7 +123,7 @@ export default Vue.extend({
|
||||
`https://api.bilibili.com/x/space/bangumi/follow/list?${params}`,
|
||||
)
|
||||
if (json.code !== 0) {
|
||||
logError(`加载订阅信息失败: ${json.message}`)
|
||||
logError(`加载番剧信息失败: ${json.message}`)
|
||||
return
|
||||
}
|
||||
const newCards: SubscriptionItem[] = lodash
|
||||
|
||||
@ -8,8 +8,8 @@ export enum SubscriptionTypes {
|
||||
const uid = getUID()
|
||||
export const subscriptions: CustomNavbarItemInit = {
|
||||
name: 'subscriptions',
|
||||
displayName: '订阅',
|
||||
content: '订阅',
|
||||
displayName: '番剧',
|
||||
content: '番剧',
|
||||
|
||||
href: `https://space.bilibili.com/${uid}/bangumi`,
|
||||
touch: true,
|
||||
|
||||
@ -1,13 +1,37 @@
|
||||
import { addComponentListener, getComponentSettings } from '@/core/settings'
|
||||
import { sq } from '@/core/spin-query'
|
||||
import { matchUrlPattern } from '@/core/utils'
|
||||
import { mainSiteUrls, matchCurrentPage } from '@/core/utils/urls'
|
||||
|
||||
const setupTransparentFill = (vm: { toggleStyle: (value: boolean, style: string) => void }) => {
|
||||
addComponentListener(
|
||||
'customNavbar.transparent',
|
||||
value => {
|
||||
if (!getComponentSettings('hideBanner').enabled) {
|
||||
vm.toggleStyle(value, 'transparent')
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
addComponentListener('hideBanner', value => {
|
||||
if (getComponentSettings('customNavbar').options.transparent) {
|
||||
vm.toggleStyle(!value, 'transparent')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const transparentFillUrls = ['//music.bilibili.com/', '//space.bilibili.com/']
|
||||
|
||||
/**
|
||||
* 检查是否应用透明填充(有横幅时)
|
||||
*/
|
||||
export const checkTransparentFill = async (vm: {
|
||||
toggleStyle: (value: boolean, style: string) => void
|
||||
}) => {
|
||||
if (transparentFillUrls.some(url => matchUrlPattern(url))) {
|
||||
setupTransparentFill(vm)
|
||||
return
|
||||
}
|
||||
if (!matchCurrentPage(mainSiteUrls)) {
|
||||
return
|
||||
}
|
||||
@ -41,19 +65,6 @@ export const checkTransparentFill = async (vm: {
|
||||
if (banner.length === 0) {
|
||||
return
|
||||
}
|
||||
addComponentListener(
|
||||
'customNavbar.transparent',
|
||||
value => {
|
||||
if (!getComponentSettings('hideBanner').enabled) {
|
||||
vm.toggleStyle(value, 'transparent')
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
addComponentListener('hideBanner', value => {
|
||||
if (getComponentSettings('customNavbar').options.transparent) {
|
||||
vm.toggleStyle(!value, 'transparent')
|
||||
}
|
||||
})
|
||||
setupTransparentFill(vm)
|
||||
})
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ export const urlInclude = [
|
||||
'//app.bilibili.com',
|
||||
'//passport.bilibili.com',
|
||||
'//live.bilibili.com/blackboard/',
|
||||
'//music.bilibili.com',
|
||||
]
|
||||
export const urlExclude = [
|
||||
'//t.bilibili.com/vote/h5/index/#/result',
|
||||
|
||||
@ -223,11 +223,20 @@
|
||||
@include theme-color();
|
||||
}
|
||||
}
|
||||
&__more {
|
||||
z-index: 1;
|
||||
}
|
||||
.bili-dyn-more {
|
||||
&:hover {
|
||||
@include background-color();
|
||||
}
|
||||
&__container:hover {
|
||||
@include background-color('3');
|
||||
}
|
||||
&__btn {
|
||||
&.tp {
|
||||
@include color('8');
|
||||
}
|
||||
&:hover {
|
||||
@include to-theme('blue');
|
||||
}
|
||||
|
||||
@ -726,9 +726,7 @@
|
||||
@include set-color('border-top-color', '4');
|
||||
}
|
||||
#{contains('mediainfo_mediaRight')} {
|
||||
#{contains('mediainfo_media_desc_section')}
|
||||
#{contains('mediainfo_display_area')}
|
||||
#{contains('mediainfo_ellipsis')} {
|
||||
#{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();
|
||||
}
|
||||
@ -995,7 +993,35 @@
|
||||
background-color: rgba(0, 0, 0, 0) !important;
|
||||
@include color('cf');
|
||||
}
|
||||
h1,h2,h3,h4,h5,h6{
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@include color('e');
|
||||
}
|
||||
}
|
||||
.bili-header {
|
||||
.notify-list-item,
|
||||
.hot-list-item {
|
||||
&:hover {
|
||||
@include background-color('3');
|
||||
}
|
||||
}
|
||||
}
|
||||
.home-empty-content__pic {
|
||||
.b-img {
|
||||
@include background-color();
|
||||
}
|
||||
}
|
||||
|
||||
.header.space-header {
|
||||
.header-toutu,
|
||||
.header-upinfo-bg-shadow {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
.header-upinfo {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
.recommended-swipe.grid-anchor {
|
||||
display: none !important;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
隐藏首页的轮播图区域.
|
||||
15
registry/lib/components/style/hide/home-carousel/index.ts
Normal file
15
registry/lib/components/style/hide/home-carousel/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'hideHomeCarousel',
|
||||
displayName: '隐藏首页轮播图',
|
||||
entry: none,
|
||||
tags: [componentsTags.style],
|
||||
urlInclude: [/^https:\/\/www\.bilibili\.com\/$/, /^https:\/\/www\.bilibili\.com\/index\.html$/],
|
||||
instantStyles: [
|
||||
{
|
||||
name: 'hide-home-carousel',
|
||||
style: () => import('./hide-home-carousel.scss'),
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -3,10 +3,10 @@
|
||||
container-name: avatar;
|
||||
// .layers:first-child,
|
||||
.layer:not(:first-child),
|
||||
.layer:not(:has(picture), :has([style*="/face/"])) {
|
||||
.layer:not(:has(picture [src*="/face/"]), :has([style*="/face/"])) {
|
||||
display: none;
|
||||
}
|
||||
.layer:is(:has(picture), :has([style*="/face/"])) {
|
||||
.layer:is(:has(picture [src*="/face/"]), :has([style*="/face/"])) {
|
||||
min-width: 100cqw;
|
||||
min-height: 100cqh;
|
||||
max-width: 100cqw;
|
||||
|
||||
@ -188,13 +188,31 @@ const downloadDash = async (
|
||||
videoDashes,
|
||||
videoCodec: codec,
|
||||
})
|
||||
const qualities = (data.accept_quality as number[])
|
||||
// .filter(qn => (
|
||||
// // 去掉当前编码不匹配的 quality
|
||||
// (video as any[]).some(d => d.id === qn && parseVideoCodec(d.codecid) === codec)
|
||||
// ))
|
||||
.map(qn => allQualities.find(q => q.value === qn))
|
||||
.filter(q => q !== undefined)
|
||||
const qualities = (() => {
|
||||
const filterByCodec = (preferredCodec: DashCodec | null) => {
|
||||
return (data.accept_quality as number[])
|
||||
.filter(qn => {
|
||||
if (preferredCodec !== null) {
|
||||
return (video as any[]).some(
|
||||
d => d.id === qn && parseVideoCodec(d.codecid) === preferredCodec,
|
||||
)
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map(qn => allQualities.find(q => q.value === qn))
|
||||
.filter(q => q !== undefined)
|
||||
}
|
||||
const allAvailableQualities = filterByCodec(codec)
|
||||
if (allAvailableQualities.length > 0) {
|
||||
return allAvailableQualities
|
||||
}
|
||||
const { options } = getComponentSettings<Options>('downloadVideo')
|
||||
const fallbackQualities = filterByCodec(options.dashCodecFallback)
|
||||
if (fallbackQualities.length > 0) {
|
||||
return fallbackQualities
|
||||
}
|
||||
return filterByCodec(null)
|
||||
})()
|
||||
const info = new DownloadVideoInfo({
|
||||
input,
|
||||
jsonData: data,
|
||||
@ -209,21 +227,21 @@ export const videoDashAvc: DownloadVideoApi = {
|
||||
name: 'video.dash.avc',
|
||||
displayName: 'dash (AVC/H.264)',
|
||||
description:
|
||||
'音画分离的 mp4 格式, 编码为 H.264, 体积较大, 兼容性较好. 下载后可以合并为单个 mp4 文件. 如果视频源没有此编码, 则会自动选择其他同清晰度的编码格式.',
|
||||
'音画分离的 mp4 格式, 编码为 H.264, 体积较大, 兼容性较好. 下载后可以合并为单个 mp4 文件. 如果视频源没有此编码, 则会回退到设置中设定的 DASH 回退编码.',
|
||||
downloadVideoInfo: async input => downloadDash(input, { codec: DashCodec.Avc }),
|
||||
}
|
||||
export const videoDashHevc: DownloadVideoApi = {
|
||||
name: 'video.dash.hevc',
|
||||
displayName: 'dash (HEVC/H.265)',
|
||||
description:
|
||||
'音画分离的 mp4 格式, 编码为 H.265, 体积中等, 兼容性较差. 下载后可以合并为单个 mp4 文件. 如果视频源没有此编码, 则会自动选择其他同清晰度的编码格式.',
|
||||
'音画分离的 mp4 格式, 编码为 H.265, 体积中等, 兼容性较差. 下载后可以合并为单个 mp4 文件. 如果视频源没有此编码, 则会回退到设置中设定的 DASH 回退编码.',
|
||||
downloadVideoInfo: async input => downloadDash(input, { codec: DashCodec.Hevc }),
|
||||
}
|
||||
export const videoDashAv1: DownloadVideoApi = {
|
||||
name: 'video.dash.av1',
|
||||
displayName: 'dash (AV1)',
|
||||
description:
|
||||
'音画分离的 mp4 格式, 编码为 AV1, 体积较小, 兼容性中等. 下载后可以合并为单个 mp4 文件. 如果视频源没有此编码, 则会自动选择其他同清晰度的编码格式.',
|
||||
'音画分离的 mp4 格式, 编码为 AV1, 体积较小, 兼容性中等. 下载后可以合并为单个 mp4 文件. 如果视频源没有此编码, 则会回退到设置中设定的 DASH 回退编码.',
|
||||
downloadVideoInfo: async input => downloadDash(input, { codec: DashCodec.Av1 }),
|
||||
}
|
||||
export const videoAudioDash: DownloadVideoApi = {
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
defineOptionsMetadata,
|
||||
} from '@/components/define'
|
||||
import { hasVideo } from '@/core/spin-query'
|
||||
import { DefaultDashExtensions } from './apis/dash'
|
||||
import { DashCodec, DefaultDashExtensions } from './apis/dash'
|
||||
|
||||
const options = defineOptionsMetadata({
|
||||
basicConfig: {
|
||||
@ -24,6 +24,11 @@ const options = defineOptionsMetadata({
|
||||
defaultValue: DefaultDashExtensions.flacAudio,
|
||||
displayName: 'DASH FLAC 音频扩展名',
|
||||
},
|
||||
dashCodecFallback: {
|
||||
defaultValue: DashCodec.Avc,
|
||||
dropdownEnum: DashCodec,
|
||||
displayName: 'DASH 回退编码',
|
||||
},
|
||||
})
|
||||
export type Options = OptionsOfMetadata<typeof options>
|
||||
export const component = defineComponentMetadata({
|
||||
|
||||
@ -4,7 +4,7 @@ import { allVideoUrls } from '@/core/utils/urls'
|
||||
|
||||
let cancel: () => void
|
||||
export const disableScrollVolume = () => {
|
||||
return preventEvent(unsafeWindow, 'mousewheel', () => {
|
||||
const shouldPrevent = () => {
|
||||
const isFullscreen = [
|
||||
'player-mode-full',
|
||||
'player-mode-web',
|
||||
@ -12,7 +12,13 @@ export const disableScrollVolume = () => {
|
||||
'player-full-win',
|
||||
].some(token => document.body.classList.contains(token))
|
||||
return isFullscreen
|
||||
})
|
||||
}
|
||||
const cancelFirefox = preventEvent(unsafeWindow, 'DOMMouseScroll', shouldPrevent)
|
||||
const cancelStandard = preventEvent(unsafeWindow, 'wheel', shouldPrevent)
|
||||
return () => {
|
||||
cancelFirefox()
|
||||
cancelStandard()
|
||||
}
|
||||
}
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'disableScrollVolume',
|
||||
|
||||
@ -3,137 +3,172 @@ import { getVueData } from '@/components/feeds/api'
|
||||
import { playerAgent } from '@/components/video/player-agent'
|
||||
import { childListSubtree, videoChange } from '@/core/observer'
|
||||
import { select } from '@/core/spin-query'
|
||||
import { playerReady } from '@/core/utils'
|
||||
import { matchUrlPattern, playerReady } from '@/core/utils'
|
||||
import { useScopedConsole } from '@/core/utils/log'
|
||||
import { videoUrls } from '@/core/utils/urls'
|
||||
import { bangumiUrls, videoUrls } from '@/core/utils/urls'
|
||||
|
||||
abstract class LegacyAutoPlay {
|
||||
isChecked(container: HTMLElement) {
|
||||
return (
|
||||
Boolean(container.querySelector('.switch-button.on') || container.matches(':checked')) ||
|
||||
container.classList.contains('on')
|
||||
)
|
||||
}
|
||||
async onRightPanelLoaded(callback: (rightPanel: Element) => Promise<void> | void) {
|
||||
await playerReady()
|
||||
const rightPanel = await Promise.any([
|
||||
select('.right-container-inner'),
|
||||
select('.playlist-container--right'),
|
||||
select('.video-pod'),
|
||||
])
|
||||
if (!rightPanel) {
|
||||
const console = useScopedConsole('传统连播模式')
|
||||
console.warn('未找到 rightPanelContainer 或 playListContainer')
|
||||
return
|
||||
}
|
||||
callback(rightPanel)
|
||||
}
|
||||
async onVideoChange(callback: (video: HTMLVideoElement) => Promise<void> | void) {
|
||||
videoChange(async () => {
|
||||
const video = (await playerAgent.query.video.element()) as HTMLVideoElement
|
||||
video?.addEventListener('play', () => callback(video), { once: true })
|
||||
})
|
||||
}
|
||||
abstract setupAutoPlay(): Promise<void>
|
||||
}
|
||||
|
||||
class VideoLegacyAutoPlay extends LegacyAutoPlay {
|
||||
readonly autoPlayControls = {
|
||||
/** 命中时应该打开连播: 传统分 P, 合集 */
|
||||
enable: [
|
||||
':is(.base-video-sections, .base-video-sections-v1) .next-button',
|
||||
':is(.multi-page, .multi-page-v1) .next-button',
|
||||
'.player-auxiliary-autoplay-switch input',
|
||||
'.video-pod .auto-play .switch-btn',
|
||||
],
|
||||
/** 命中时应该关闭连播: 关联视频推荐 */
|
||||
disable: [
|
||||
':is(.recommend-list, .recommend-list-v1) .next-button',
|
||||
'.recommend-list-v1 .switch-btn',
|
||||
],
|
||||
}
|
||||
/** 最后 1P 时不能开启连播 */
|
||||
readonly disableConditions = [
|
||||
/** 传统分 P */
|
||||
() =>
|
||||
Boolean(
|
||||
dq(
|
||||
':is(.multi-page, .multi-page-v1) .list-box li.on:last-child, .video-pod__list .simple-base-item.active:last-child',
|
||||
),
|
||||
),
|
||||
/** 替代分 P 的合集 & 可分子合集的分 P 合集, 布局长得比下面那个丑一点 */
|
||||
() =>
|
||||
Boolean(
|
||||
dq(
|
||||
'.video-sections-item:last-child .video-episode-card:last-child .video-episode-card__info-playing',
|
||||
),
|
||||
),
|
||||
/** 可分子合集的合集 */
|
||||
() =>
|
||||
Boolean(
|
||||
dq(
|
||||
'.video-sections-item:last-child .video-episode-card:last-child .video-episode-card__info-title-playing',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
async checkPlayMode() {
|
||||
const element = (await select(
|
||||
[...this.autoPlayControls.disable, ...this.autoPlayControls.enable].join(','),
|
||||
)) as HTMLElement
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
const shouldChecked =
|
||||
this.autoPlayControls.enable.some(selector => element.matches(selector)) &&
|
||||
this.disableConditions.every(condition => !condition())
|
||||
const checked = this.isChecked(element)
|
||||
console.log('checkPlayMode', { shouldChecked, checked })
|
||||
if (shouldChecked !== checked) {
|
||||
element.click()
|
||||
}
|
||||
}
|
||||
async checkPlayListPlayMode(rightPanel: Element) {
|
||||
// spell-checker: disable
|
||||
const videoSequentialNumber = dq(rightPanel, '.list-count, .video-pod__header .amt')
|
||||
const sequentialNumbers = videoSequentialNumber.innerHTML
|
||||
.replace(/[()]/g, '')
|
||||
.split('/')
|
||||
.map(it => parseInt(it))
|
||||
const lastVideoElement = dq(
|
||||
'.action-list .action-list-item-wrap:last-child .action-list-item .actionlist-item-inner, .video-pod__list .pod-item:last-child',
|
||||
)
|
||||
|
||||
const isSingleList =
|
||||
lastVideoElement.classList.contains('singlep-list-item-inner') ||
|
||||
lastVideoElement.querySelector('.single-p') !== null
|
||||
const isLastVideo = (() => {
|
||||
if (isSingleList) {
|
||||
return (
|
||||
lastVideoElement.classList.contains('siglep-active') ||
|
||||
lastVideoElement.querySelector('.simple-base-item.active') !== null
|
||||
)
|
||||
}
|
||||
return lastVideoElement.children[1].lastElementChild.classList.contains(
|
||||
'multip-list-item-active',
|
||||
)
|
||||
})()
|
||||
|
||||
const shouldContinue = !(sequentialNumbers[0] >= sequentialNumbers[1] && isLastVideo)
|
||||
|
||||
console.log('checkPlayListPlayMode', { isLastVideo, sequentialNumbers, shouldContinue })
|
||||
const app = document.getElementById('app')
|
||||
const vueInstance = getVueData(app)
|
||||
// 不用判断当前状态是什么,直接将将是否需要继续播放赋值 vue 实例中的 continuousPlay
|
||||
vueInstance.setContinuousPlay(shouldContinue)
|
||||
// spell-checker: enable
|
||||
}
|
||||
|
||||
async setupAutoPlay() {
|
||||
this.onRightPanelLoaded(rightPanel => {
|
||||
const check = () => {
|
||||
const isPlayList = rightPanel.querySelector('.video-pod__list.section')
|
||||
return isPlayList ? this.checkPlayListPlayMode(rightPanel) : this.checkPlayMode()
|
||||
}
|
||||
childListSubtree(rightPanel, () => check())
|
||||
this.onVideoChange(() => check())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class BangumiLegacyAutoPlay extends LegacyAutoPlay {
|
||||
async setupAutoPlay() {
|
||||
const autoPlayRadio = (await select(
|
||||
'.bpx-player-ctrl-setting-handoff input[type="radio"][value="0"]',
|
||||
)) as HTMLInputElement
|
||||
if (autoPlayRadio === null) {
|
||||
const console = useScopedConsole('传统连播模式')
|
||||
console.warn('未找到 autoPlayRadio')
|
||||
return
|
||||
}
|
||||
autoPlayRadio.click()
|
||||
}
|
||||
}
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'legacyAutoPlay',
|
||||
displayName: '传统连播模式',
|
||||
description: '模拟传统的多 P 连播策略: 仅连播视频的分 P, 最后 1P 放完禁止连播其他推荐视频.',
|
||||
tags: [componentsTags.video],
|
||||
urlInclude: videoUrls,
|
||||
urlInclude: [...videoUrls, ...bangumiUrls],
|
||||
entry: async () => {
|
||||
const console = useScopedConsole('传统连播模式')
|
||||
const autoPlayControls = {
|
||||
// 命中时应该打开连播: 传统分 P, 合集
|
||||
enable: [
|
||||
':is(.base-video-sections, .base-video-sections-v1) .next-button',
|
||||
':is(.multi-page, .multi-page-v1) .next-button',
|
||||
'.player-auxiliary-autoplay-switch input',
|
||||
'.video-pod .auto-play .switch-btn',
|
||||
],
|
||||
// 命中时应该关闭连播: 关联视频推荐
|
||||
disable: [
|
||||
':is(.recommend-list, .recommend-list-v1) .next-button',
|
||||
'.recommend-list-v1 .switch-btn',
|
||||
],
|
||||
}
|
||||
// 最后 1P 时不能开启连播
|
||||
const disableConditions = [
|
||||
// 传统分 P
|
||||
() =>
|
||||
Boolean(
|
||||
dq(
|
||||
':is(.multi-page, .multi-page-v1) .list-box li.on:last-child, .video-pod__list .simple-base-item.active:last-child',
|
||||
),
|
||||
),
|
||||
// 替代分 P 的合集
|
||||
// & 可分子合集的分 P 合集, 布局长得比下面那个丑一点
|
||||
() =>
|
||||
Boolean(
|
||||
dq(
|
||||
'.video-sections-item:last-child .video-episode-card:last-child .video-episode-card__info-playing',
|
||||
),
|
||||
),
|
||||
// 可分子合集的合集
|
||||
() =>
|
||||
Boolean(
|
||||
dq(
|
||||
'.video-sections-item:last-child .video-episode-card:last-child .video-episode-card__info-title-playing',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
await playerReady()
|
||||
|
||||
// 初始化状态
|
||||
const rightPanel = await Promise.any([
|
||||
select('.right-container-inner'),
|
||||
select('.playlist-container--right'),
|
||||
select('.video-pod'),
|
||||
])
|
||||
// 如果未找到元素,提前退出不执行后续动作
|
||||
if (!rightPanel) {
|
||||
console.warn('未找到 rightPanelContainer 或 playListContainer')
|
||||
return
|
||||
}
|
||||
|
||||
const checkPlayListPlayMode = async () => {
|
||||
const videoSequentialNumber = dq(rightPanel, '.list-count, .video-pod__header .amt')
|
||||
const sequentialNumbers = videoSequentialNumber.innerHTML
|
||||
.replace(/[()]/g, '')
|
||||
.split('/')
|
||||
.map(it => parseInt(it))
|
||||
const lastVideoElement = dq(
|
||||
'.action-list .action-list-item-wrap:last-child .action-list-item .actionlist-item-inner, .video-pod__list .pod-item:last-child',
|
||||
)
|
||||
|
||||
const isSingleList =
|
||||
lastVideoElement.classList.contains('singlep-list-item-inner') ||
|
||||
lastVideoElement.querySelector('.single-p') !== null
|
||||
const isLastVideo = (() => {
|
||||
if (isSingleList) {
|
||||
return (
|
||||
lastVideoElement.classList.contains('siglep-active') ||
|
||||
lastVideoElement.querySelector('.simple-base-item.active') !== null
|
||||
)
|
||||
}
|
||||
return lastVideoElement.children[1].lastElementChild.classList.contains(
|
||||
'multip-list-item-active',
|
||||
)
|
||||
})()
|
||||
|
||||
const shouldContinue = !(sequentialNumbers[0] >= sequentialNumbers[1] && isLastVideo)
|
||||
|
||||
console.log('checkPlayListPlayMode', { isLastVideo, sequentialNumbers, shouldContinue })
|
||||
const app = document.getElementById('app')
|
||||
const vueInstance = getVueData(app)
|
||||
// 不用判断当前状态是什么,直接将将是否需要继续播放赋值 vue 实例中的 continuousPlay
|
||||
vueInstance.setContinuousPlay(shouldContinue)
|
||||
}
|
||||
|
||||
const isChecked = (container: HTMLElement) =>
|
||||
Boolean(container.querySelector('.switch-button.on') || container.matches(':checked')) ||
|
||||
container.classList.contains('on')
|
||||
|
||||
const checkPlayMode = async () => {
|
||||
const element = (await select(
|
||||
[...autoPlayControls.disable, ...autoPlayControls.enable].join(','),
|
||||
)) as HTMLElement
|
||||
if (!element) {
|
||||
return
|
||||
const legacyAutoPlay = (() => {
|
||||
const isBangumi = bangumiUrls.some(url => matchUrlPattern(url))
|
||||
if (isBangumi) {
|
||||
return new BangumiLegacyAutoPlay()
|
||||
}
|
||||
const shouldChecked =
|
||||
autoPlayControls.enable.some(selector => element.matches(selector)) &&
|
||||
disableConditions.every(condition => !condition())
|
||||
const checked = isChecked(element)
|
||||
console.log('checkPlayMode', { shouldChecked, checked })
|
||||
if (shouldChecked !== checked) {
|
||||
element.click()
|
||||
}
|
||||
}
|
||||
|
||||
const checkRightPanelPlayMode = async () => {
|
||||
const isPlayList = rightPanel.querySelector('.video-pod__list.section')
|
||||
return isPlayList ? checkPlayListPlayMode() : checkPlayMode()
|
||||
}
|
||||
|
||||
videoChange(async () => {
|
||||
const video = (await playerAgent.query.video.element()) as HTMLVideoElement
|
||||
video?.addEventListener('play', () => checkRightPanelPlayMode(), { once: true })
|
||||
})
|
||||
|
||||
childListSubtree(rightPanel, () => checkRightPanelPlayMode())
|
||||
return new VideoLegacyAutoPlay()
|
||||
})()
|
||||
await legacyAutoPlay.setupAutoPlay()
|
||||
},
|
||||
})
|
||||
|
||||
@ -8,10 +8,16 @@ import desc from './desc.md'
|
||||
import ScreenshotContainer from './VideoScreenshotContainer.vue'
|
||||
|
||||
export const VideoScreenshotDisabledClass = 'video-screenshot-disable'
|
||||
const entry = async () => {
|
||||
let screenShotsList: Vue & {
|
||||
screenshots: Screenshot[]
|
||||
let screenShotsList: Vue & {
|
||||
screenshots: Screenshot[]
|
||||
}
|
||||
const exitConfirmHandler = (e: BeforeUnloadEvent) => {
|
||||
if (screenShotsList?.screenshots.length > 0) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const entry = async () => {
|
||||
addControlBarButton({
|
||||
name: 'takeScreenshot',
|
||||
displayName: '截图',
|
||||
@ -35,6 +41,7 @@ const entry = async () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
window.addEventListener('beforeunload', exitConfirmHandler)
|
||||
}
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'videoScreenshot',
|
||||
@ -45,8 +52,14 @@ export const component = defineComponentMetadata({
|
||||
'zh-CN': desc,
|
||||
},
|
||||
urlInclude: playerUrls,
|
||||
reload: () => document.body.classList.remove(VideoScreenshotDisabledClass),
|
||||
unload: () => document.body.classList.add(VideoScreenshotDisabledClass),
|
||||
reload: () => {
|
||||
document.body.classList.remove(VideoScreenshotDisabledClass)
|
||||
window.addEventListener('beforeunload', exitConfirmHandler)
|
||||
},
|
||||
unload: () => {
|
||||
document.body.classList.add(VideoScreenshotDisabledClass)
|
||||
window.removeEventListener('beforeunload', exitConfirmHandler)
|
||||
},
|
||||
plugin: {
|
||||
displayName: '视频截图 - 快捷键支持',
|
||||
setup: ({ addData }) => {
|
||||
|
||||
46
registry/lib/plugins/style/custom-navbar-pgc/index.ts
Normal file
46
registry/lib/plugins/style/custom-navbar-pgc/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { PluginMetadata } from '@/plugins/plugin'
|
||||
import type { CustomNavbarItemInit } from '../../../components/style/custom-navbar/custom-navbar-item'
|
||||
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'customNavbar.items.pgc',
|
||||
displayName: '自定义顶栏 - 版权内容',
|
||||
description: '为自定义顶栏扩充版权内容相关的快速入口, 包括国创 / 电影 / 电视剧 /综艺 / 纪录片',
|
||||
async setup({ addData }) {
|
||||
addData('customNavbar.items', (items: CustomNavbarItemInit[]) => {
|
||||
const pgcItems = [
|
||||
{
|
||||
name: 'guochuang',
|
||||
displayName: '国创',
|
||||
href: 'https://www.bilibili.com/guochuang/',
|
||||
},
|
||||
{
|
||||
name: 'movie',
|
||||
displayName: '电影',
|
||||
href: 'https://www.bilibili.com/movie/',
|
||||
},
|
||||
{
|
||||
name: 'tv',
|
||||
displayName: '电视剧',
|
||||
href: 'https://www.bilibili.com/tv/',
|
||||
},
|
||||
{
|
||||
name: 'variety',
|
||||
displayName: '综艺',
|
||||
href: 'https://www.bilibili.com/variety/',
|
||||
},
|
||||
{
|
||||
name: 'documentary',
|
||||
displayName: '纪录片',
|
||||
href: 'https://www.bilibili.com/documentary/',
|
||||
},
|
||||
]
|
||||
items.push(
|
||||
...pgcItems.map(item => ({
|
||||
...item,
|
||||
content: item.displayName,
|
||||
active: document.URL.startsWith(item.href),
|
||||
})),
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
||||
@ -7,14 +7,14 @@ import { DownloadVideoOutput } from '../../../../components/video/download/types
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'downloadVideo.outputs.idm',
|
||||
displayName: '下载视频 - IDM 输出支持',
|
||||
description:
|
||||
'为下载视频增加 IDM 输出支持,建议配合 [ef2.exe](https://github.com/MotooriKashin/ef2) 以简化操作、保留文件名.',
|
||||
description: '为下载视频增加 IDM 输出支持.',
|
||||
setup: ({ addData }) => {
|
||||
addData('downloadVideo.outputs', (outputs: DownloadVideoOutput[]) => {
|
||||
outputs.push({
|
||||
name: 'idm',
|
||||
displayName: 'IDM',
|
||||
description: '使用 IDM 的 .ef2 格式导出, 可以在 IDM 中使用导入开始下载.',
|
||||
description:
|
||||
'使用 IDM 的 .ef2 格式导出, 可以在 IDM 中使用导入开始下载, 建议配合 [ef2.exe](https://github.com/MotooriKashin/ef2) 以简化操作、保留文件名.',
|
||||
runAction: async action => {
|
||||
const { infos } = action
|
||||
const referer = document.URL.replace(window.location.search, '')
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.9.5",
|
||||
"version": "2.9.6",
|
||||
"author": "Grant Howard, Coulomb-G",
|
||||
"copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)",
|
||||
"license": "MIT",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
tabindex="0"
|
||||
class="be-launch-bar-action-item suggest-item"
|
||||
class="be-launch-bar-action-item be-launch-bar-suggest-item"
|
||||
:title="action.displayName || action.name"
|
||||
:data-indexer="action.indexer"
|
||||
@click.self="performAction($event)"
|
||||
@ -10,27 +10,35 @@
|
||||
@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)">
|
||||
<div class="be-launch-bar-suggest-item-content">
|
||||
<div
|
||||
v-if="action.icon"
|
||||
class="be-launch-bar-suggest-item-icon"
|
||||
@click="performAction($event)"
|
||||
>
|
||||
<VIcon :icon="action.icon" :size="18" />
|
||||
</div>
|
||||
<div class="suggest-item-title" @click="performAction($event)">
|
||||
<div class="be-launch-bar-suggest-item-title" @click="performAction($event)">
|
||||
<component
|
||||
:is="action.content"
|
||||
v-if="action.content"
|
||||
class="suggest-item-name"
|
||||
class="be-launch-bar-suggest-item-name"
|
||||
:name="action.name"
|
||||
></component>
|
||||
<div v-else class="suggest-item-name">
|
||||
<div v-else class="be-launch-bar-suggest-item-name">
|
||||
{{ action.displayName || action.name }}
|
||||
</div>
|
||||
<div v-if="action.description" class="suggest-item-description" :title="action.description">
|
||||
<div
|
||||
v-if="action.description"
|
||||
class="be-launch-bar-suggest-item-description"
|
||||
:title="action.description"
|
||||
>
|
||||
{{ action.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="action.deleteAction"
|
||||
class="suggest-item-delete"
|
||||
class="be-launch-bar-suggest-item-delete"
|
||||
title="删除此项"
|
||||
@click="performDelete($event)"
|
||||
>
|
||||
@ -72,7 +80,7 @@ export default Vue.extend({
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
|
||||
.suggest-item {
|
||||
.be-launch-bar-suggest-item {
|
||||
outline: none !important;
|
||||
padding: 6px 6px 6px 10px;
|
||||
cursor: pointer;
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</div>
|
||||
<div ref="list" class="launch-bar-suggest-list">
|
||||
<div v-if="isHistory" class="launch-bar-history-list">
|
||||
<div v-if="actions.length === 0" class="history-empty suggest-item disabled" tabindex="0">
|
||||
<div v-if="actions.length === 0" class="history-empty be-launch-bar-suggest-item disabled" tabindex="0">
|
||||
暂无搜索历史
|
||||
</div>
|
||||
<ActionItem
|
||||
@ -30,7 +30,7 @@
|
||||
:action="a"
|
||||
@previous-item="previousItem()"
|
||||
@next-item="nextItem()"
|
||||
@delete-item="onDeleteItem()"
|
||||
@delete-item="onDeleteItem(index)"
|
||||
@action="
|
||||
index === actions.length - 1 && onClearHistory()
|
||||
onAction(a)
|
||||
@ -41,20 +41,20 @@
|
||||
<VEmpty
|
||||
v-if="actions.length === 0 && noActions"
|
||||
tabindex="0"
|
||||
class="suggest-item disabled"
|
||||
class="be-launch-bar-suggest-item disabled"
|
||||
></VEmpty>
|
||||
<VLoading
|
||||
v-if="actions.length === 0 && !noActions"
|
||||
tabindex="0"
|
||||
class="suggest-item disabled"
|
||||
class="be-launch-bar-suggest-item disabled"
|
||||
></VLoading>
|
||||
<ActionItem
|
||||
v-for="a of actions"
|
||||
v-for="(a, index) of actions"
|
||||
:key="a.key"
|
||||
:action="a"
|
||||
@previous-item="previousItem()"
|
||||
@next-item="nextItem()"
|
||||
@delete-item="onDeleteItem()"
|
||||
@delete-item="onDeleteItem(index)"
|
||||
@action="onAction(a)"
|
||||
/>
|
||||
</div>
|
||||
@ -234,7 +234,8 @@ export default Vue.extend({
|
||||
this.focusTarget.next()
|
||||
e.preventDefault()
|
||||
},
|
||||
handleIndexUpdate() {
|
||||
async handleIndexUpdate() {
|
||||
await this.$nextTick()
|
||||
if (!this.focusTarget.hasFocus) {
|
||||
this.focusInput()
|
||||
return
|
||||
@ -248,7 +249,8 @@ export default Vue.extend({
|
||||
this.focusTarget.next()
|
||||
},
|
||||
search,
|
||||
onDeleteItem() {
|
||||
onDeleteItem(index: number) {
|
||||
this.focusTarget.setFocus(index)
|
||||
this.focusTarget.previous()
|
||||
this.getActions()
|
||||
},
|
||||
@ -263,7 +265,7 @@ export default Vue.extend({
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
focusSuggestItem(nth: number) {
|
||||
this.$refs.list.querySelector(`.suggest-item:nth-child(${nth})`)?.focus()
|
||||
this.$refs.list.querySelector(`.be-launch-bar-suggest-item:nth-child(${nth})`)?.focus()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -26,6 +26,9 @@ export class FocusTarget extends EventTarget {
|
||||
return this.itemIndex > -1
|
||||
}
|
||||
|
||||
setFocus(index: number) {
|
||||
this.index = index
|
||||
}
|
||||
reset(length: number, index = this.index) {
|
||||
this.itemLength = length
|
||||
this.index = index
|
||||
|
||||
@ -1875,8 +1875,32 @@
|
||||
{
|
||||
"name": "新歌热榜",
|
||||
"report": "musicplus",
|
||||
"url": "//www.bilibili.com/v/musicplus/",
|
||||
"url": "//music.bilibili.com/pc/music-center/",
|
||||
"icon": "musicplus"
|
||||
},
|
||||
{
|
||||
"name": "综艺",
|
||||
"report": "variety",
|
||||
"url": "//www.bilibili.com/variety/",
|
||||
"icon": "zongyi"
|
||||
},
|
||||
{
|
||||
"name": "VLOG",
|
||||
"report": "vlog",
|
||||
"url": "//www.bilibili.com/v/life/daily/?tag=530003",
|
||||
"icon": "VLOG"
|
||||
},
|
||||
{
|
||||
"name": "搞笑",
|
||||
"report": "funny",
|
||||
"url": "//www.bilibili.com/v/life/funny/",
|
||||
"icon": "gaoxiao"
|
||||
},
|
||||
{
|
||||
"name": "单机游戏",
|
||||
"report": "stand_alone",
|
||||
"url": "//www.bilibili.com/v/game/stand_alone/",
|
||||
"icon": "danjiyouxi"
|
||||
}
|
||||
]
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user