mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge tag 'v2.8.9-preview' into preview-fixes
This commit is contained in:
commit
092cdbf866
@ -0,0 +1 @@
|
||||
将动态中左右切换式的图片改回传统的平铺展示. (在动态详情中可能稍有延迟)
|
||||
45
registry/lib/components/feeds/legacy-image-viewer/index.ts
Normal file
45
registry/lib/components/feeds/legacy-image-viewer/index.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { forEachFeedsCard, getVueData } from '@/components/feeds/api'
|
||||
import { feedsUrls } from '@/core/utils/urls'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'legacyFeedsImageViewer',
|
||||
displayName: '动态图片平铺展示',
|
||||
tags: [componentsTags.feeds],
|
||||
urlInclude: feedsUrls,
|
||||
entry: () => {
|
||||
forEachFeedsCard({
|
||||
added: card => {
|
||||
const vueData = getVueData(card.element)
|
||||
|
||||
// 普通动态
|
||||
const path = 'data.modules.module_dynamic.major.opus.style'
|
||||
const imageViewerStyle: number | null = lodash.get(vueData, path, null)
|
||||
if (imageViewerStyle === 1) {
|
||||
lodash.set(vueData, path, undefined)
|
||||
return
|
||||
}
|
||||
|
||||
// 动态详情
|
||||
const modules = vueData?.data?.modules
|
||||
if (Array.isArray(modules)) {
|
||||
const moduleTop = modules.find(m => m.module_type === 'MODULE_TYPE_TOP')
|
||||
const moduleContent = modules.find(m => m.module_type === 'MODULE_TYPE_CONTENT')
|
||||
const album = moduleTop?.module_top?.display?.album
|
||||
const paragraphs: any[] = moduleContent?.module_content?.paragraphs
|
||||
if (album && paragraphs) {
|
||||
modules.splice(modules.indexOf(moduleTop), 1)
|
||||
paragraphs.push({
|
||||
align: 0,
|
||||
para_type: 2,
|
||||
pic: {
|
||||
pics: album.pics,
|
||||
style: 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -11,7 +11,7 @@
|
||||
<script lang="ts">
|
||||
import { TabControl, VIcon } from '@/ui'
|
||||
import { FeedsCardType, feedsCardTypes } from '@/components/feeds/api'
|
||||
import { getNotifyCount } from '@/components/feeds/notify'
|
||||
import { getNotifyCountByType } from '@/components/feeds/notify'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { tabs } from './tabs/tabs'
|
||||
|
||||
@ -49,7 +49,7 @@ export default Vue.extend({
|
||||
if (!feedsCardType.apiType) {
|
||||
return
|
||||
}
|
||||
const count = await getNotifyCount(feedsCardType.apiType)
|
||||
const count = await getNotifyCountByType(feedsCardType.apiType)
|
||||
tab.count = count
|
||||
console.log(tab)
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<span
|
||||
title="稍后再看"
|
||||
class="watchlater be-outer-watchlater video-toolbar-left-item"
|
||||
:class="{ on }"
|
||||
:class="{ on, ...displayModeClass }"
|
||||
@click="toggle()"
|
||||
>
|
||||
<VIcon class="icon" :size="28" icon="mdi-timetable"></VIcon>
|
||||
@ -14,14 +14,18 @@
|
||||
<script lang="ts">
|
||||
import { VIcon } from '@/ui'
|
||||
import { watchlaterList, toggleWatchlater } from '@/components/video/watchlater'
|
||||
import { DisplayMode, Options } from './options'
|
||||
import { addComponentListener, getComponentSettings } from '@/core/settings'
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
VIcon,
|
||||
},
|
||||
data() {
|
||||
const { displayMode } = getComponentSettings<Options>('outerWatchlater').options
|
||||
return {
|
||||
watchlaterList,
|
||||
displayMode,
|
||||
aid: unsafeWindow.aid,
|
||||
tipText: '',
|
||||
tipShowing: false,
|
||||
@ -33,6 +37,17 @@ export default Vue.extend({
|
||||
console.log(this.watchlaterList, this.aid, this.watchlaterList.includes(parseInt(this.aid)))
|
||||
return this.watchlaterList.includes(parseInt(this.aid))
|
||||
},
|
||||
displayModeClass() {
|
||||
return {
|
||||
'icon-only': this.displayMode === DisplayMode.Icon,
|
||||
'icon-and-text': this.displayMode === DisplayMode.IconAndText,
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
addComponentListener('outerWatchlater.displayMode', (value: DisplayMode) => {
|
||||
this.displayMode = value
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
showTip(text: string) {
|
||||
@ -62,12 +77,22 @@ export default Vue.extend({
|
||||
margin-right: 28px !important;
|
||||
position: relative;
|
||||
width: auto !important;
|
||||
@media screen and (max-width: 1340px), (max-height: 750px) {
|
||||
|
||||
@mixin icon-only {
|
||||
margin-right: max(calc(min(11vw, 11vh) - 117.2px), 6px) !important;
|
||||
.text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.icon-only {
|
||||
@include icon-only();
|
||||
}
|
||||
&:not(.icon-and-text) {
|
||||
@media screen and (max-width: 1340px), (max-height: 750px) {
|
||||
@include icon-only();
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
|
||||
@ -1,22 +1,10 @@
|
||||
import {
|
||||
defineComponentMetadata,
|
||||
defineOptionsMetadata,
|
||||
OptionsOfMetadata,
|
||||
} from '@/components/define'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { ComponentEntry } from '@/components/types'
|
||||
import { getUID, matchUrlPattern, mountVueComponent } from '@/core/utils'
|
||||
import { videoUrls, watchlaterUrls } from '@/core/utils/urls'
|
||||
import { KeyBindingAction } from '../../utils/keymap/bindings'
|
||||
import { addVideoActionButton } from '@/components/video/video-actions'
|
||||
|
||||
const options = defineOptionsMetadata({
|
||||
showInWatchlaterPages: {
|
||||
defaultValue: false,
|
||||
displayName: '在稍后再看页面中仍然显示',
|
||||
},
|
||||
})
|
||||
|
||||
type Options = OptionsOfMetadata<typeof options>
|
||||
import { Options, options } from './options'
|
||||
|
||||
const entry: ComponentEntry<Options> = async ({ settings }) => {
|
||||
if (watchlaterUrls.some(matchUrlPattern) && !settings.options.showInWatchlaterPages) {
|
||||
|
||||
20
registry/lib/components/video/outer-watchlater/options.ts
Normal file
20
registry/lib/components/video/outer-watchlater/options.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { defineOptionsMetadata, OptionsOfMetadata } from '@/components/define'
|
||||
|
||||
export enum DisplayMode {
|
||||
Auto = '自动',
|
||||
Icon = '图标',
|
||||
IconAndText = '图标 + 文字',
|
||||
}
|
||||
export const options = defineOptionsMetadata({
|
||||
showInWatchlaterPages: {
|
||||
defaultValue: false,
|
||||
displayName: '在稍后再看页面中仍然显示',
|
||||
},
|
||||
displayMode: {
|
||||
defaultValue: DisplayMode.Auto,
|
||||
displayName: '显示方式',
|
||||
dropdownEnum: DisplayMode,
|
||||
},
|
||||
})
|
||||
|
||||
export type Options = OptionsOfMetadata<typeof options>
|
||||
@ -2,7 +2,7 @@
|
||||
<span
|
||||
class="quick-favorite be-quick-favorite video-toolbar-left-item"
|
||||
title="快速收藏"
|
||||
:class="{ on: isFavorite }"
|
||||
:class="{ on: isFavorite, ...displayModeClass }"
|
||||
@click.left.self="toggle()"
|
||||
@click.right.prevent.self="listShowing = !listShowing"
|
||||
>
|
||||
@ -33,12 +33,13 @@
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { addComponentListener, getComponentSettings } from '@/core/settings'
|
||||
import { getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
|
||||
import { getUID, getCsrf } from '@/core/utils'
|
||||
import { logError } from '@/core/utils/log'
|
||||
import { Toast } from '@/core/toast'
|
||||
import { VDropdown } from '@/ui'
|
||||
import { DisplayMode, Options } from './options'
|
||||
|
||||
const { options } = getComponentSettings('quickFavorite')
|
||||
export default Vue.extend({
|
||||
@ -46,6 +47,7 @@ export default Vue.extend({
|
||||
VDropdown,
|
||||
},
|
||||
data() {
|
||||
const { displayMode } = getComponentSettings<Options>('outerWatchlater').options
|
||||
return {
|
||||
aid: unsafeWindow.aid,
|
||||
favoriteTitle: '',
|
||||
@ -56,8 +58,17 @@ export default Vue.extend({
|
||||
lists: [],
|
||||
selectedFavorite: '<未选择>',
|
||||
listShowing: false,
|
||||
displayMode,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayModeClass() {
|
||||
return {
|
||||
'icon-only': this.displayMode === DisplayMode.Icon,
|
||||
'icon-and-text': this.displayMode === DisplayMode.IconAndText,
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedFavorite(value: string) {
|
||||
if (this.lists.length === 0) {
|
||||
@ -101,6 +112,9 @@ export default Vue.extend({
|
||||
},
|
||||
created() {
|
||||
this.syncFavoriteState()
|
||||
addComponentListener('quickFavorite.displayMode', (value: DisplayMode) => {
|
||||
this.displayMode = value
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async syncFavoriteState() {
|
||||
@ -192,12 +206,22 @@ export default Vue.extend({
|
||||
.text {
|
||||
display: inline;
|
||||
}
|
||||
@media screen and (max-width: 1340px), (max-height: 750px) {
|
||||
|
||||
@mixin icon-only {
|
||||
margin-right: max(calc(min(11vw, 11vh) - 117.2px), 6px) !important;
|
||||
.text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.icon-only {
|
||||
@include icon-only();
|
||||
}
|
||||
&:not(.icon-and-text) {
|
||||
@media screen and (max-width: 1340px), (max-height: 750px) {
|
||||
@include icon-only();
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
font-family: 'quick-favorite' !important;
|
||||
font-size: 28px;
|
||||
|
||||
@ -1,28 +1,11 @@
|
||||
import {
|
||||
defineComponentMetadata,
|
||||
defineOptionsMetadata,
|
||||
OptionsOfMetadata,
|
||||
} from '@/components/define'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { ComponentEntry } from '@/components/types'
|
||||
import { getUID, matchUrlPattern, mountVueComponent } from '@/core/utils'
|
||||
import { favoriteListUrls, videoUrls } from '@/core/utils/urls'
|
||||
import { KeyBindingAction } from '../../utils/keymap/bindings'
|
||||
import { addVideoActionButton } from '@/components/video/video-actions'
|
||||
import { videoChange } from '@/core/observer'
|
||||
|
||||
const options = defineOptionsMetadata({
|
||||
favoriteFolderID: {
|
||||
defaultValue: 0,
|
||||
displayName: '快速收藏夹ID',
|
||||
hidden: true,
|
||||
},
|
||||
showInFavoritePages: {
|
||||
defaultValue: false,
|
||||
displayName: '在收藏夹播放页面仍然显示',
|
||||
},
|
||||
})
|
||||
|
||||
type Options = OptionsOfMetadata<typeof options>
|
||||
import { options, Options } from './options'
|
||||
|
||||
const entry: ComponentEntry<Options> = async ({ settings }) => {
|
||||
if (favoriteListUrls.some(matchUrlPattern) && !settings.options.showInFavoritePages) {
|
||||
|
||||
25
registry/lib/components/video/quick-favorite/options.ts
Normal file
25
registry/lib/components/video/quick-favorite/options.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { defineOptionsMetadata, OptionsOfMetadata } from '@/components/define'
|
||||
|
||||
export enum DisplayMode {
|
||||
Auto = '自动',
|
||||
Icon = '图标',
|
||||
IconAndText = '图标 + 文字',
|
||||
}
|
||||
export const options = defineOptionsMetadata({
|
||||
favoriteFolderID: {
|
||||
defaultValue: 0,
|
||||
displayName: '快速收藏夹ID',
|
||||
hidden: true,
|
||||
},
|
||||
showInFavoritePages: {
|
||||
defaultValue: false,
|
||||
displayName: '在收藏夹播放页面仍然显示',
|
||||
},
|
||||
displayMode: {
|
||||
defaultValue: DisplayMode.Auto,
|
||||
displayName: '显示方式',
|
||||
dropdownEnum: DisplayMode,
|
||||
},
|
||||
})
|
||||
|
||||
export type Options = OptionsOfMetadata<typeof options>
|
||||
1
registry/lib/plugins/feeds/filter/hide-goods/index.md
Normal file
1
registry/lib/plugins/feeds/filter/hide-goods/index.md
Normal file
@ -0,0 +1 @@
|
||||
移除动态里的商品带货动态 (UP主的推荐 · 来自 XX), 装有 `动态过滤器` 时生效.
|
||||
24
registry/lib/plugins/feeds/filter/hide-goods/index.ts
Normal file
24
registry/lib/plugins/feeds/filter/hide-goods/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { PluginMetadata } from '@/plugins/plugin'
|
||||
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'feedsFilter.pluginBlocks.goods',
|
||||
displayName: '动态过滤器 - 移除商品带货动态',
|
||||
async setup() {
|
||||
const { forEachFeedsCard, getVueData } = await import('@/components/feeds/api')
|
||||
forEachFeedsCard({
|
||||
added: card => {
|
||||
const vueData = getVueData(card.element)
|
||||
const additionalType: string | null = lodash.get(
|
||||
vueData,
|
||||
'data.modules.module_dynamic.additional.type',
|
||||
null,
|
||||
)
|
||||
const isGoods = additionalType === 'ADDITIONAL_TYPE_GOODS'
|
||||
if (!isGoods) {
|
||||
return
|
||||
}
|
||||
card.element.classList.add('plugin-block')
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
64
registry/lib/plugins/video/download/wasm-output/database.ts
Normal file
64
registry/lib/plugins/video/download/wasm-output/database.ts
Normal file
@ -0,0 +1,64 @@
|
||||
const DB_NAME = 'bilibili-evolved-wasm-output'
|
||||
const DB_VERSION = 124
|
||||
|
||||
export const storeNames = {
|
||||
cache: 'cache',
|
||||
} as const
|
||||
|
||||
async function database() {
|
||||
return new Promise((reslove: (db: IDBDatabase) => void, reject) => {
|
||||
const req = unsafeWindow.indexedDB.open(DB_NAME, DB_VERSION)
|
||||
req.onerror = reject
|
||||
req.onupgradeneeded = () => {
|
||||
const db = req.result
|
||||
for (const name of db.objectStoreNames) {
|
||||
db.deleteObjectStore(name)
|
||||
}
|
||||
Object.values(storeNames).forEach(v => {
|
||||
db.createObjectStore(v)
|
||||
})
|
||||
}
|
||||
req.onsuccess = () => reslove(req.result)
|
||||
})
|
||||
}
|
||||
|
||||
async function objectStore(name: string, mode?: IDBTransactionMode) {
|
||||
return database().then(
|
||||
db =>
|
||||
new Promise((reslove: (db: IDBObjectStore) => void, reject) => {
|
||||
const tr = db.transaction(name, mode)
|
||||
reslove(tr.objectStore(name))
|
||||
tr.onerror = reject
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async function get(store: IDBObjectStore, key: IDBValidKey | IDBKeyRange) {
|
||||
return new Promise((reslove: (db: any) => void, reject) => {
|
||||
const res = store.get(key)
|
||||
res.onerror = reject
|
||||
res.onsuccess = () => reslove(res.result)
|
||||
})
|
||||
}
|
||||
|
||||
async function put(store: IDBObjectStore, value: any, key?: IDBValidKey) {
|
||||
return new Promise((reslove: (db: IDBValidKey) => void, reject) => {
|
||||
const res = store.put(value, key)
|
||||
res.onerror = reject
|
||||
res.onsuccess = () => reslove(res.result)
|
||||
})
|
||||
}
|
||||
|
||||
export async function getOrLoad<K extends keyof typeof storeNames, V>(
|
||||
storeName: K,
|
||||
key: IDBValidKey,
|
||||
loader: (key: IDBValidKey) => V,
|
||||
) {
|
||||
const value: V = await objectStore(storeName).then(store => get(store, key))
|
||||
if (value) {
|
||||
return value
|
||||
}
|
||||
const newValue = await loader(key)
|
||||
await objectStore(storeName, 'readwrite').then(store => put(store, newValue, key))
|
||||
return newValue
|
||||
}
|
||||
@ -2,26 +2,35 @@ import { DownloadPackage } from '@/core/download'
|
||||
import { meta } from '@/core/meta'
|
||||
import { Toast } from '@/core/toast'
|
||||
import { FFmpeg } from './ffmpeg'
|
||||
import { httpGet, toBlobUrl, toastProgress } from './utils'
|
||||
import { getCacheOrGet, httpGet, toastProgress, toBlobUrl } from './utils'
|
||||
|
||||
const ffmpeg = new FFmpeg()
|
||||
|
||||
async function load(toast: Toast) {
|
||||
await ffmpeg.load({
|
||||
workerLoadURL: await toBlobUrl(
|
||||
meta.compilationInfo.altCdn.library.ffmpeg.worker,
|
||||
workerLoadURL: toBlobUrl(
|
||||
await getCacheOrGet(
|
||||
'ffmpeg-worker',
|
||||
meta.compilationInfo.altCdn.library.ffmpeg.worker,
|
||||
toastProgress(toast, '正在加载 FFmpeg Worker'),
|
||||
),
|
||||
'text/javascript',
|
||||
toastProgress(toast, '正在加载 FFmpeg Worker'),
|
||||
),
|
||||
coreURL: await toBlobUrl(
|
||||
meta.compilationInfo.altCdn.library.ffmpeg.core,
|
||||
coreURL: toBlobUrl(
|
||||
await getCacheOrGet(
|
||||
'ffmpeg-core',
|
||||
meta.compilationInfo.altCdn.library.ffmpeg.core,
|
||||
toastProgress(toast, '正在加载 FFmpeg Core'),
|
||||
),
|
||||
'text/javascript',
|
||||
toastProgress(toast, '正在加载 FFmpeg Core'),
|
||||
),
|
||||
wasmURL: await toBlobUrl(
|
||||
meta.compilationInfo.altCdn.library.ffmpeg.wasm,
|
||||
wasmURL: toBlobUrl(
|
||||
await getCacheOrGet(
|
||||
'ffmpeg-wasm',
|
||||
meta.compilationInfo.altCdn.library.ffmpeg.wasm,
|
||||
toastProgress(toast, '正在加载 FFmpeg WASM'),
|
||||
),
|
||||
'application/wasm',
|
||||
toastProgress(toast, '正在加载 FFmpeg WASM'),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Toast } from '@/core/toast'
|
||||
import { formatFileSize, formatPercent } from '@/core/utils/formatters'
|
||||
import { getOrLoad, storeNames } from './database'
|
||||
|
||||
type OnProgress = (received: number, length: number) => void
|
||||
|
||||
@ -47,8 +48,11 @@ export async function httpGet(url: string, onprogress: OnProgress) {
|
||||
return chunksAll
|
||||
}
|
||||
|
||||
export async function toBlobUrl(url: string, mimeType: string, onprogress: OnProgress) {
|
||||
const buffer = await httpGet(url, onprogress)
|
||||
export async function getCacheOrGet(key: string, url: string, loading: OnProgress) {
|
||||
return getOrLoad(storeNames.cache, key, async () => httpGet(url, loading))
|
||||
}
|
||||
|
||||
export function toBlobUrl(buffer: Uint8Array, mimeType: string) {
|
||||
const blob = new Blob([buffer], { type: mimeType })
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
@ -32,7 +32,8 @@ export const updateLatestID = (cards: { id: string }[]) => {
|
||||
const [id] = [...cards.map(c => c.id)].sort(compareID).reverse()
|
||||
setLatestID(id)
|
||||
}
|
||||
export const getNotifyCount = async (type = 'all'): Promise<number> => {
|
||||
/** 按类型获取动态提醒数 */
|
||||
export const getNotifyCountByType = async (type: string): Promise<number> => {
|
||||
const api = `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all/update?type=${type}&update_baseline=${getLatestID()}`
|
||||
const json = await getJsonWithCredentials(api)
|
||||
if (json.code !== 0) {
|
||||
@ -40,3 +41,14 @@ export const getNotifyCount = async (type = 'all'): Promise<number> => {
|
||||
}
|
||||
return lodash.get(json, 'data.update_num', 0)
|
||||
}
|
||||
/** 获取所有类型的动态提醒数 (视频, 番剧, 专栏; 普通动态不算)
|
||||
* @see https://github.com/the1812/Bilibili-Evolved/issues/4427
|
||||
*/
|
||||
export const getNotifyCount = async (): Promise<number> => {
|
||||
const api = 'https://api.bilibili.com/x/web-interface/dynamic/entrance'
|
||||
const json = await getJsonWithCredentials(api)
|
||||
if (json.code !== 0) {
|
||||
return 0
|
||||
}
|
||||
return lodash.get(json, 'data.update_info.item.count', 0)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user