mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge branch 'preview-fixes' into preview-features
This commit is contained in:
commit
1555e26137
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -100,6 +100,7 @@
|
||||
"Vuex",
|
||||
"Wasm",
|
||||
"watchlater",
|
||||
"webfullscreen",
|
||||
"woff",
|
||||
"xmlhttp"
|
||||
],
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
<!-- spell-checker: disable -->
|
||||
|
||||
<div align="center"><img id="Bilibili-Evolved" width="500" alt="Bilibili Evolved" src="./images/bilibili-evolved-wide-color.svg"></div>
|
||||
<br>
|
||||
<div align="center">
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
</div>
|
||||
<div class="be-live-list-item-info">
|
||||
<div class="be-live-list-item-title" :title="item.title">
|
||||
{{ item.title }}
|
||||
{{ decodeTitle(item.title) }}
|
||||
</div>
|
||||
<div class="be-live-list-item-user" :title="item.uname">
|
||||
{{ item.uname }}
|
||||
@ -52,6 +52,12 @@
|
||||
import { VIcon, TextBox, DpiImage, VEmpty, VLoading } from '@/ui'
|
||||
import { getJsonWithCredentials, responsiveGetPages } from '@/core/ajax'
|
||||
|
||||
const decodeTitle = (title: string) => {
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.innerHTML = title
|
||||
return textArea.value
|
||||
}
|
||||
|
||||
interface LiveInfo {
|
||||
cover: string
|
||||
face: string
|
||||
@ -98,6 +104,7 @@ export default Vue.extend({
|
||||
this.refresh()
|
||||
},
|
||||
methods: {
|
||||
decodeTitle,
|
||||
async refresh() {
|
||||
try {
|
||||
this.items = []
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
.bili-dyn-content,
|
||||
.dyn-card-opus__summary:first-child,
|
||||
.card .main-content {
|
||||
.bili-rich-text__action,
|
||||
.expand-btn,
|
||||
|
||||
1
registry/lib/components/feeds/full-content/index.md
Normal file
1
registry/lib/components/feeds/full-content/index.md
Normal file
@ -0,0 +1 @@
|
||||
不管内容多长, 总是完全展开动态的内容. (专栏不算)
|
||||
@ -1,13 +1,16 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { toggleStyle } from '@/components/styled-component'
|
||||
import { feedsUrlsWithoutDetail } from '@/core/utils/urls'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
...toggleStyle('fullFeedsContent', () => import('./full-content.scss')),
|
||||
name: 'fullFeedsContent',
|
||||
instantStyles: [
|
||||
{
|
||||
name: 'full-feeds-content',
|
||||
style: () => import('./full-content.scss'),
|
||||
},
|
||||
],
|
||||
displayName: '展开动态内容',
|
||||
description: {
|
||||
'zh-CN': '不管内容多长, 总是完全展开动态的内容.',
|
||||
},
|
||||
tags: [componentsTags.style, componentsTags.feeds],
|
||||
urlInclude: feedsUrlsWithoutDetail,
|
||||
entry: none,
|
||||
})
|
||||
|
||||
@ -43,6 +43,10 @@ export const entry: ComponentEntry = async ({ metadata: { name } }) => {
|
||||
true,
|
||||
)
|
||||
}
|
||||
// https://github.com/the1812/Bilibili-Evolved/issues/4459
|
||||
if (matchUrlPattern('https://www.bilibili.com/account/history')) {
|
||||
document.body.classList.add('history-page')
|
||||
}
|
||||
const CustomNavbar = await import('./CustomNavbar.vue')
|
||||
const customNavbar: Vue & {
|
||||
styles: string[]
|
||||
|
||||
@ -32,10 +32,13 @@ li.nav-item[report-id="playpage_dynamic"] .i-frame,
|
||||
height: var(--navbar-height) !important;
|
||||
}
|
||||
#message-navbar,
|
||||
#biliMainHeader:not(.z-top-container),
|
||||
body:not(.history-page) #biliMainHeader:not(.z-top-container),
|
||||
.z-top-container:not(#biliMainHeader) {
|
||||
max-height: var(--navbar-height) !important;
|
||||
}
|
||||
body.history-page #biliMainHeader:not(.z-top-container) {
|
||||
height: auto;
|
||||
}
|
||||
.link-top-container#tab-container {
|
||||
top: var(--navbar-height) !important;
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
|
||||
import { formData, getCsrf } from '@/core/utils'
|
||||
import { getCsrf } from '@/core/utils'
|
||||
import { descendingSort } from '@/core/utils/sort'
|
||||
import {
|
||||
VButton,
|
||||
@ -119,7 +119,14 @@ import {
|
||||
DpiImage,
|
||||
} from '@/ui'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { types, TypeFilter, HistoryItem, getHistoryItems, group, HistoryType } from './types'
|
||||
import {
|
||||
navbarFilterTypes,
|
||||
TypeFilter,
|
||||
HistoryItem,
|
||||
getHistoryItems,
|
||||
group,
|
||||
HistoryType,
|
||||
} from './types'
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -135,7 +142,7 @@ export default Vue.extend({
|
||||
mixins: [popperMixin],
|
||||
data() {
|
||||
return {
|
||||
types,
|
||||
types: navbarFilterTypes,
|
||||
search: '',
|
||||
viewTime: 0,
|
||||
cards: [],
|
||||
@ -164,10 +171,11 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggleTypeFilter(typeFilter: TypeFilter) {
|
||||
types.forEach(t => (t.checked = t.name === typeFilter.name))
|
||||
navbarFilterTypes.forEach(t => (t.checked = t.name === typeFilter.name))
|
||||
this.reloadHistoryItems()
|
||||
},
|
||||
async reloadHistoryItems() {
|
||||
this.cards = []
|
||||
this.viewTime = 0
|
||||
this.hasMorePage = true
|
||||
this.loading = true
|
||||
@ -178,8 +186,8 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
filterFunc(item: HistoryItem) {
|
||||
const isAllType = types.find(it => it.name === HistoryType.All).checked
|
||||
if (!isAllType && types.some(t => t.name === item.type && !t.checked)) {
|
||||
const isAllType = navbarFilterTypes.find(it => it.name === HistoryType.All).checked
|
||||
if (!isAllType && navbarFilterTypes.some(t => t.name === item.type && !t.checked)) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
@ -196,7 +204,7 @@ export default Vue.extend({
|
||||
async nextPage() {
|
||||
const items = await getHistoryItems(
|
||||
this.viewTime,
|
||||
types.find(t => t.checked),
|
||||
navbarFilterTypes.find(t => t.checked),
|
||||
)
|
||||
const cards: HistoryItem[] = lodash.uniqBy(
|
||||
this.cards.concat(items).sort(descendingSort((item: HistoryItem) => item.viewAt)),
|
||||
@ -228,10 +236,10 @@ export default Vue.extend({
|
||||
this.paused = targetState
|
||||
await postTextWithCredentials(
|
||||
'https://api.bilibili.com/x/v2/history/shadow/set',
|
||||
formData({
|
||||
new URLSearchParams({
|
||||
csrf: getCsrf(),
|
||||
switch: targetState,
|
||||
}),
|
||||
switch: targetState.toString(),
|
||||
}).toString(),
|
||||
)
|
||||
} catch (error) {
|
||||
this.paused = !targetState
|
||||
|
||||
@ -9,6 +9,7 @@ export enum HistoryType {
|
||||
Live = 'live',
|
||||
Article = 'article',
|
||||
Bangumi = 'pgc',
|
||||
Cheese = 'cheese',
|
||||
}
|
||||
export interface HistoryItem {
|
||||
id: string | number
|
||||
@ -51,8 +52,8 @@ export interface TypeFilter {
|
||||
checked: boolean
|
||||
apiType: string
|
||||
}
|
||||
/** 所有的历史记录类型 */
|
||||
export const types = [
|
||||
/** 顶栏可筛选的历史记录类型 */
|
||||
export const navbarFilterTypes = [
|
||||
{
|
||||
name: HistoryType.All,
|
||||
displayName: '全部',
|
||||
@ -72,6 +73,7 @@ export const types = [
|
||||
displayName: '番剧',
|
||||
icon: 'mdi-television-classic',
|
||||
checked: false,
|
||||
// b 站的历史不区分视频和番剧, 只能混着拿然后过滤
|
||||
apiType: 'archive',
|
||||
},
|
||||
{
|
||||
@ -122,14 +124,14 @@ const formatTime = (date: Date) => {
|
||||
.padStart(2, '0')}`
|
||||
}
|
||||
const parseHistoryItem = (item: any): HistoryItem => {
|
||||
if (item.history.business === 'article') {
|
||||
if (item.history.business === HistoryType.Article) {
|
||||
item.history.cid = item.history.oid
|
||||
}
|
||||
const {
|
||||
epid, // 番剧 ep号
|
||||
bvid, // 视频 bv号
|
||||
cid, // 专栏 cv号
|
||||
oid, // 直播 房间号 / 专栏 cv 号
|
||||
oid, // 直播 房间号 / 专栏 cv 号 / 课程神秘标识符
|
||||
} = item.history
|
||||
const progressParam = item.progress > 0 ? `t=${item.progress}` : 't=0'
|
||||
const progress = item.progress === -1 ? 1 : item.progress / item.duration
|
||||
@ -159,6 +161,17 @@ const parseHistoryItem = (item: any): HistoryItem => {
|
||||
upFaceUrl: https(item.author_face),
|
||||
upID: item.author_mid,
|
||||
}
|
||||
if (item.history.business === HistoryType.Cheese) {
|
||||
return {
|
||||
...commonInfo,
|
||||
id: oid,
|
||||
upName: item.title,
|
||||
title: item.show_title,
|
||||
url: item.uri,
|
||||
/** 特殊处理: 展示上分类仍归类到视频 */
|
||||
type: HistoryType.Video,
|
||||
}
|
||||
}
|
||||
if (epid) {
|
||||
return {
|
||||
...commonInfo,
|
||||
@ -215,7 +228,15 @@ export const getHistoryItems = async (viewTime?: number, type?: TypeFilter) => {
|
||||
if (!Array.isArray(list)) {
|
||||
return []
|
||||
}
|
||||
return (list as any[]).map(parseHistoryItem)
|
||||
return (list as any[]).map(parseHistoryItem).filter(it => {
|
||||
if (it === null) {
|
||||
return false
|
||||
}
|
||||
if (type && type.name !== HistoryType.All) {
|
||||
return it.type === type.name
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 为历史记录项目分组, 包含`今天`, `昨天`, `本周`, `更早`
|
||||
|
||||
@ -27,6 +27,9 @@ export const checkTransparentFill = async (vm: {
|
||||
if ((banner as HTMLVideoElement | HTMLImageElement).src) {
|
||||
return true
|
||||
}
|
||||
if (banner.querySelector('.animated-banner')) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (banners.some(hasBannerImage)) {
|
||||
|
||||
113
registry/lib/components/style/dark-mode/README.md
Normal file
113
registry/lib/components/style/dark-mode/README.md
Normal file
@ -0,0 +1,113 @@
|
||||
<!-- spell-checker: disable -->
|
||||
# 关于夜间模式
|
||||
|
||||
## 历史
|
||||
夜间模式是 Bilibili-Evolved 最早出现的功能之一 (2018 年), 早期的技术水平很差, 留下了很多难以治理的代码. (位于 `./old/dark-slice-*.css`)
|
||||
主要问题有:
|
||||
- 配色没有做成 CSS Variables, 后续无法再更换主题.
|
||||
- 纯 CSS 编写, 嵌套样式产生了大量冗余代码.
|
||||
- 选择器条件太宽泛, 经常造成预期外的样式影响.
|
||||
- 同一模块的代码分散在不同地方, 难以二次修改.
|
||||
|
||||
因此夜间模式为了缓解这些问题做过不少调整, 现在的组成如下:
|
||||
|
||||
## 文件组成
|
||||
|
||||
### `dark-mode.scss`
|
||||
主入口样式, 只 `@import` 其他样式.
|
||||
|
||||
### `_dark-definition.scss`
|
||||
提供辅助编写夜间模式样式的 Sass Mixin 和 Function, 主要功能如下:
|
||||
|
||||
#### 修改颜色
|
||||
|
||||
- `@include color($level)`: 设置 `color`.
|
||||
> 参数例如 `@include color('e')` 会展开为 `color: #eee !important`. 类似的简写还有 `'ef'` -> `#efefef`, `'123'` -> `#123`, `123456` -> `#123456`, 如果参数留空则为透明: `@include color()` -> `color: transparent !important`, 此规则适用于所有修改颜色的 Mixin.
|
||||
- `@include background-color($level)`: 设置 `background-color`.
|
||||
- `@include background($level)`: 设置 `background`, 和上一条不同的是, 这条会同时去除 `background-image`.
|
||||
- `@include border-color($level)`: 设置 `border-color`.
|
||||
- `@include fill($level)`: 设置 `fill`. (用于 SVG)
|
||||
- `@include stroke($level)`: 设置 `stroke`. (用于 SVG)
|
||||
|
||||
#### 特殊颜色
|
||||
|
||||
- 上述的[修改颜色](#修改颜色)中的 6 条 Mixin 都可以加上 `theme-` 前缀直接使用主题色作为参数, 例如 `@include theme-color()` -> `color: var(--theme-color) !important`.
|
||||
- 加上 `foreground-` 前缀可以使用文本颜色 (通用设置里那个), 例如 `@include foreground-color()` -> `color: var(--foreground-color) !important`.
|
||||
- `color` 和 `background-color` 可以加上 `vip-` 前缀使用大会员的粉色, 例如 `@include vip-color()` -> `color: $vip-color !important`.
|
||||
|
||||
#### 滤镜
|
||||
|
||||
通常用于处理无法直接设置颜色的图片, 会影响全部子元素.
|
||||
|
||||
- `@include to-white()`: 将内容转为白色.
|
||||
- `@include to-gray($invert)`: 将内容转为灰色 (单色), 参数 0~1 可控制灰度值.
|
||||
- `@include grayscale($value)`: 将内容黑白化, 同 CSS grayscale.
|
||||
- `@include brightness($value)`: 修改亮度, 同 CSS brightness.
|
||||
- `@include to-theme($from)`: 将单一颜色的图片转换为主题颜色 (近似值), 参数是原图片的颜色, 可选 `'pink'` 或 `'blue'`.
|
||||
|
||||
#### 其他
|
||||
|
||||
- `@include no-image()`: 删除元素的 `background-image`.
|
||||
- `@include no-shadow()`: 删除元素的 `box-shadow`.
|
||||
- `@include theme-shadow()`: 将元素的 `box-shadow` 设为主题色. (现不再推荐使用这个, 而是设置正常的黑色投影即可)
|
||||
- `@include hide($hideLayout)`: 隐藏元素, 正常来说也很少用到, 实在是在夜间模式下多余且无法修改其样式的元素才考虑隐藏.
|
||||
- `#{contains($selector)}`: 生成一个只要 class 包含 `$selector` 就能命中的选择器, 通常用于番剧区带有 hash 的不稳定 class 名. 例如:
|
||||
```scss
|
||||
#{contains('mediainfo_media_toolbar')} {
|
||||
@include color('a');
|
||||
}
|
||||
```
|
||||
将生成
|
||||
```css
|
||||
[class*='mediainfo_media_toolbar'] {
|
||||
color: #aaa !important;
|
||||
}
|
||||
```
|
||||
|
||||
### `dark-navbar.scss`
|
||||
处理部分顶栏样式的旧代码, 不应再新增代码.
|
||||
|
||||
### `dark-variables.scss`
|
||||
b 站官方提供的暗色配色, 能够为较新的 b 站组件开启夜间模式配色, 并且将主题色更换为了脚本中设置的主题色.
|
||||
|
||||
### `dark-urls.ts`
|
||||
排除的 URL 列表, 这些页面通常更新非常频繁, 难以及时适配; 或者使用频率很低不做适配.
|
||||
|
||||
## 开发调试指南
|
||||
|
||||
参考 [CONTRIBUTING.md](../../../../../CONTRIBUTING.md) 启动开发环境, 正常启动 `夜间模式` 的调试即可, 所有对 Sass / CSS 的修改都支持热重载, 如果改动 TS 代码则需要手动刷新页面.
|
||||
|
||||
### 规范
|
||||
|
||||
代码编写的规范基本对应[历史](#历史)一节中提到的问题:
|
||||
- 优先使用 [_dark-definition.scss](#_dark-definitionscss) 提供的封装来修改样式.
|
||||
- 新增代码应该在最新的 `dark-slice-*.scss` 文件中增加, 不应修改 `./old/dark-slice-*.css`.
|
||||
- 每个 `dark-slice-*.scss` 文件存放约 1000 行左右代码, 超过应新建一个文件.
|
||||
- 对同一模块的样式修改, 应使用嵌套语法写在一处.
|
||||
- 修改样式时, 选择器至少要有两个有区分度的层级, 避免样式意外影响其他地方.
|
||||
|
||||
例如以下代码, 修改的元素是 `.container`, 这个 class 在各种地方都可能出现, 所以需要向上找到能表示当前模块的一个 class `.van-popper-login` 缩小范围.
|
||||
```scss
|
||||
.van-popper-login .container {
|
||||
@include border-color("4");
|
||||
}
|
||||
```
|
||||
|
||||
### 配色
|
||||
|
||||
转换为夜间模式后, 配色的对比度应尽可能对应原版, 推荐的配色方案参考:
|
||||
|
||||
| 分类 | 原颜色 | 夜间模式颜色 |
|
||||
| ------------------------------ | ----------- | ------------------------------------------------------------------- |
|
||||
| 一级文字颜色 | 黑色 | `@include color('e')` |
|
||||
| 二级文字颜色 | 灰色 | `@include color('a')` |
|
||||
| 页面背景 | 白色 | `@include background-color('2')` |
|
||||
| 页面一级内容区域 / 卡片 / 弹窗 | 灰色 | `@include background-color('3')` |
|
||||
| 页面二级内容区域 / 卡片 / 弹窗 | 深灰色 | `@include background-color('4')` |
|
||||
| 链接 | 蓝色 | `@include theme-color()` |
|
||||
| 主要按钮背景色 | 蓝色 | `@include theme-background-color()` |
|
||||
| 主要按钮文字颜色 | 白色 | `@include foreground-color()` |
|
||||
| 次要按钮背景色 | 灰色 | `@include background-color('3')` |
|
||||
| 次要按钮文字颜色 | 黑色 / 灰色 | `@include color('e')` / `@include color('a')` |
|
||||
| 占位区域 (placeholder) | 灰色 | `@include background-color('3')` |
|
||||
| 分割线 | 灰色 | `@include background-color('4')` / `@include background-color('5')` |
|
||||
@ -86,6 +86,9 @@ $vip-color: 'FB7299';
|
||||
@mixin foreground-fill($level: '') {
|
||||
@include set-foreground-color('fill', $level);
|
||||
}
|
||||
@mixin foreground-stroke($level: '') {
|
||||
@include set-foreground-color('stroke', $level);
|
||||
}
|
||||
|
||||
@mixin to-white {
|
||||
filter: brightness(0) invert(1) !important;
|
||||
|
||||
@ -3,8 +3,14 @@
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default Vue.extend({})
|
||||
<script setup lang="ts">
|
||||
const params = new URLSearchParams(location.search)
|
||||
if (params.has('bvid')) {
|
||||
const restParams = new URLSearchParams([...params.entries()].filter(([name]) => name !== 'bvid'))
|
||||
location.replace(
|
||||
`/video/${params.get('bvid')}${restParams.size > 0 ? `?${restParams.toString()}` : ''}`,
|
||||
)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
使用重新设计的清爽风格首页替换原本的首页.
|
||||
|
||||
请注意, 此功能与 `极简首页` 互斥, 请勿同时使用.
|
||||
请注意:
|
||||
- 此功能与 `极简首页` 互斥, 请勿同时使用.
|
||||
- 此功能会禁用首页的[悬浮视频](https://github.com/the1812/Bilibili-Evolved/discussions/4404), 变为直接跳转到视频页面.
|
||||
|
||||
- 个性化推荐: 启用时展示推荐视频, 禁用时展示热门视频
|
||||
选项说明:
|
||||
- 个性化推荐: 启用时展示推荐视频, 禁用时展示热门视频.
|
||||
- 启用横向滚动: 在可以横向滚动的列表中, 使鼠标滚轮可以做出横向滚动的效果, 请注意这会禁用卡片的边缘吸附效果.
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
使用重新设计的极简首页替换原本的首页.
|
||||
|
||||
请注意, 此功能与 `清爽首页` 互斥, 请勿同时使用.
|
||||
请注意:
|
||||
- 此功能与 `清爽首页` 互斥, 请勿同时使用.
|
||||
- 此功能会禁用首页的[悬浮视频](https://github.com/the1812/Bilibili-Evolved/discussions/4404), 变为直接跳转到视频页面.
|
||||
|
||||
选项说明:
|
||||
- 个性化推荐: 启用时展示推荐视频, 禁用时展示热门视频
|
||||
- 自定义列数: 为 `0` 时根据视图宽度推断, 大于 `0` 的值将作为固定的列数
|
||||
@ -4,7 +4,7 @@
|
||||
<VideoCard v-for="c of cards" :key="c.id" :data="c" />
|
||||
</div>
|
||||
<VEmpty v-if="!loading && cards.length === 0" />
|
||||
<ScrollTrigger v-if="!error" @trigger="loadCards" />
|
||||
<ScrollTrigger v-if="!error" ref="scrollTrigger" detect-viewport @trigger="loadCards" />
|
||||
<MinimalHomeOperations v-if="cards.length > 0" @refresh="refresh" />
|
||||
</div>
|
||||
</template>
|
||||
@ -43,6 +43,7 @@ export default Vue.extend({
|
||||
try {
|
||||
this.error = false
|
||||
this.loading = true
|
||||
this.$refs.scrollTrigger.setLoadState('loading')
|
||||
this.cards = lodash.uniqBy(
|
||||
[...this.cards, ...(await getVideoFeeds('video', this.lastID))],
|
||||
it => it.id,
|
||||
@ -50,12 +51,17 @@ export default Vue.extend({
|
||||
} catch (error) {
|
||||
logError(error)
|
||||
this.error = true
|
||||
this.$refs.scrollTrigger.setLoadState('error')
|
||||
} finally {
|
||||
this.loading = false
|
||||
if (this.loaded) {
|
||||
this.$refs.scrollTrigger.setLoadState('loaded')
|
||||
}
|
||||
}
|
||||
},
|
||||
async refresh() {
|
||||
this.cards = []
|
||||
this.$refs.scrollTrigger.resetIsFirstLoad()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -15,8 +15,8 @@ export const presetBase: Record<string, string> = {
|
||||
playerMenu: '`',
|
||||
longJumpForward: 'j',
|
||||
longJumpBackward: 'shift j',
|
||||
jumpBackward: 'arrowLeft',
|
||||
jumpForward: 'arrowRight',
|
||||
jumpBackward: '',
|
||||
jumpForward: '',
|
||||
danmaku: 'd',
|
||||
seekBegin: '0',
|
||||
sendComment: 'ctrl enter',
|
||||
|
||||
@ -9,6 +9,7 @@ const entry = async () => {
|
||||
if (isNotHtml() || isIframe()) {
|
||||
return
|
||||
}
|
||||
/* spell-checker: disable */
|
||||
const builtInNoClean = ['videocard_series']
|
||||
const [noClean] = registerAndGetData('urlParamsClean.noClean', builtInNoClean)
|
||||
const builtInBlockParams = [
|
||||
@ -61,6 +62,7 @@ const entry = async () => {
|
||||
'subarea_rank',
|
||||
'popular_rank',
|
||||
'launch_id',
|
||||
'spmid',
|
||||
]
|
||||
const [blockParams] = registerAndGetData('urlParamsClean.params', builtInBlockParams)
|
||||
const builtInSiteSpecifiedParams = [
|
||||
@ -89,6 +91,7 @@ const entry = async () => {
|
||||
param: 'noReffer',
|
||||
},
|
||||
]
|
||||
/* spell-checker: enable */
|
||||
const [siteSpecifiedParams] = registerAndGetData(
|
||||
'urlParamsClean.siteSpecifiedParams',
|
||||
builtInSiteSpecifiedParams,
|
||||
@ -138,9 +141,17 @@ const entry = async () => {
|
||||
url: string,
|
||||
...restArgs: unknown[]
|
||||
) {
|
||||
const newUrl = getCleanUrl(url)
|
||||
const resolvedUrl = (() => {
|
||||
try {
|
||||
return new URL(url, location.origin + location.pathname).toString()
|
||||
} catch (error) {
|
||||
console.warn('History API URL', `解析失败: ${url}`)
|
||||
return url
|
||||
}
|
||||
})()
|
||||
const newUrl = getCleanUrl(resolvedUrl)
|
||||
if (newUrl !== url) {
|
||||
console.log('History API 拦截', url, newUrl)
|
||||
console.log('History API 拦截', resolvedUrl, newUrl)
|
||||
return original.call(this, data, unused, newUrl, ...restArgs)
|
||||
}
|
||||
return original.call(this, data, unused, url, ...restArgs)
|
||||
@ -170,5 +181,6 @@ export const component = defineComponentMetadata({
|
||||
displayName,
|
||||
entry,
|
||||
tags: [componentsTags.utils],
|
||||
/* spell-checker: disable */
|
||||
urlExclude: [/game\.bilibili\.com\/fgo/, /live\.bilibili\.com\/p\/html\/live-app-hotrank\//],
|
||||
})
|
||||
|
||||
@ -57,7 +57,9 @@ const linkProviders: LinkProvider[] = [
|
||||
newQuery.set(key, value)
|
||||
}
|
||||
}
|
||||
return `https://www.bilibili.com/video/${id}${newQuery ? `?${newQuery.toString()}` : ''}`
|
||||
return `https://www.bilibili.com/video/${id}${
|
||||
newQuery.size > 0 ? `?${newQuery.toString()}` : ''
|
||||
}`
|
||||
},
|
||||
]
|
||||
export default Vue.extend({
|
||||
|
||||
@ -28,7 +28,7 @@ export const festivalUrls = [/\/\/www\.bilibili\.com\/festival\//]
|
||||
/** 含有普通视频的页面 */
|
||||
export const videoUrls = ['//www.bilibili.com/video/', ...festivalUrls, ...mediaListUrls]
|
||||
/** 含有番剧的页面 */
|
||||
export const bangumiUrls = ['//www.bilibili.com/bangumi/']
|
||||
export const bangumiUrls = ['//www.bilibili.com/bangumi/play/']
|
||||
/** 含有课程的页面 */
|
||||
export const cheeseUrls = ['//www.bilibili.com/cheese/']
|
||||
/** 视频和番剧页面 */
|
||||
|
||||
@ -7,23 +7,89 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { useScopedConsole } from '@/core/utils/log'
|
||||
import { delay } from '@/core/utils/'
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
VLoading: () => import('./VLoading.vue').then(m => m.default),
|
||||
},
|
||||
props: {
|
||||
detectViewport: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFirstLoad: true,
|
||||
isViewportTriggerRunning: false,
|
||||
parentLoadState: 'none',
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
const console = useScopedConsole('ScrollTrigger')
|
||||
const element = this.$el as HTMLElement
|
||||
const { visible } = await import('@/core/observer')
|
||||
visible(element, records => {
|
||||
visible(element, async records => {
|
||||
if (records.some(r => r.intersectionRatio > 0)) {
|
||||
console.log('Intersection Observer trigger')
|
||||
this.trigger()
|
||||
|
||||
if (!this.detectViewport && !this.isFirstLoad && this.isViewportTriggerRunning) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isViewportTriggerRunning = true
|
||||
while (this.getVisibleStateByViewport(element)) {
|
||||
await delay(500)
|
||||
|
||||
// 需要父组件配合 setLoadState,不然会一直 continue
|
||||
// 确认父组件加载状态,等待加载完成后再继续执行,否则会出现多次加载的情况
|
||||
if (this.parentLoadState !== 'loaded') {
|
||||
continue
|
||||
}
|
||||
|
||||
// 再次确认元素可视情况,避免当元素已不可视时仍然触发加载
|
||||
if (!this.getVisibleStateByViewport(element)) {
|
||||
break
|
||||
}
|
||||
|
||||
console.log('is first load & viewport trigger')
|
||||
this.trigger()
|
||||
}
|
||||
this.isFirstLoad = false
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 当元素顶部位于视口内部时返回为正数,位于视口外部时返回为负数,正好位于视口底部时返回为零。
|
||||
* @param element HTML 元素
|
||||
* @returns 元素顶部到视口底部的距离
|
||||
*/
|
||||
getElementToViewportBottomDistance(element: HTMLElement): number {
|
||||
return document.documentElement.clientHeight - element.getBoundingClientRect().top
|
||||
},
|
||||
|
||||
/**
|
||||
* 当元素顶部在视口内部时返回 true。
|
||||
* 为避免极端情况,当元素顶部到视口底部的距离大于 -20 时同样返回 true。
|
||||
* @param element HTML 元素
|
||||
* @returns 元素可视情况
|
||||
*/
|
||||
getVisibleStateByViewport(element: HTMLElement): boolean {
|
||||
return Boolean(this.getElementToViewportBottomDistance(element) > -20)
|
||||
},
|
||||
|
||||
setLoadState(loadState: 'loading' | 'error' | 'loaded') {
|
||||
this.parentLoadState = loadState
|
||||
},
|
||||
|
||||
resetIsFirstLoad() {
|
||||
this.isFirstLoad = true
|
||||
},
|
||||
|
||||
trigger() {
|
||||
this.$emit('trigger')
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user