mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Add feeds cards manager v2
This commit is contained in:
parent
512cf8e218
commit
3be42acde2
@ -3,7 +3,7 @@ import { childList, childListSubtree } from '@/core/observer'
|
||||
import { select } from '@/core/spin-query'
|
||||
import { liveUrls } from '@/core/utils/urls'
|
||||
import { addData } from '@/plugins/data'
|
||||
import { FeedsCardsManager } from '.'
|
||||
import { FeedsCardsManager } from './base'
|
||||
|
||||
/** 表示一个动态卡片列表适配器, 可以对在不同页面的地方里的动态列表提供支持 */
|
||||
export interface FeedsCardsListAdaptor {
|
||||
@ -133,7 +133,7 @@ addData(ListAdaptorKey, (adaptors: FeedsCardsListAdaptor[]) => {
|
||||
'https://t.bilibili.com/',
|
||||
],
|
||||
watchCardsList: async manager => {
|
||||
const list = await select('.feed-card .content, .detail-content .detail-card') as HTMLElement
|
||||
const list = await select('.feed-card .content, .detail-content .detail-card, .bili-dyn-list__items') as HTMLElement
|
||||
if (!list) {
|
||||
return false
|
||||
}
|
||||
|
||||
81
src/components/feeds/api/manager/base.ts
Normal file
81
src/components/feeds/api/manager/base.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { matchUrlPattern } from '@/core/utils'
|
||||
import { registerAndGetData } from '@/plugins/data'
|
||||
import { FeedsCardCallback, FeedsCard } from '../types'
|
||||
import { ListAdaptorKey, FeedsCardsListAdaptor } from './adaptor'
|
||||
|
||||
export const feedsCardCallbacks: Required<FeedsCardCallback>[] = []
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
export const getVueData = (el: any) => el.__vue__ || el.parentElement.__vue__
|
||||
|
||||
export const createNodeValidator = (className: string) => (node: Node): node is HTMLElement => (
|
||||
node && node instanceof HTMLElement && node.parentNode && node.classList.contains(className)
|
||||
)
|
||||
|
||||
/** 动态卡片管理器支持的自定义事件 */
|
||||
export enum FeedsCardsManagerEventType {
|
||||
/** 新增动态卡片 */
|
||||
AddCard = 'addCard',
|
||||
/** 移除动态卡片 */
|
||||
RemoveCard = 'removeCard',
|
||||
}
|
||||
|
||||
/** 动态卡片管理器, 封装了动态卡片的解析与监测 */
|
||||
export abstract class FeedsCardsManager extends EventTarget {
|
||||
/** 是否已在监测中 */
|
||||
watching = false
|
||||
/** 监测到的动态卡片列表 */
|
||||
cards: FeedsCard[] = []
|
||||
/** 监听动态卡片的增加/移除 */
|
||||
addEventListener(
|
||||
type: FeedsCardsManagerEventType,
|
||||
listener: (event: CustomEvent<FeedsCard>) => void,
|
||||
options?: boolean | AddEventListenerOptions | undefined,
|
||||
): void {
|
||||
super.addEventListener(type, listener, options)
|
||||
}
|
||||
/** 取消监听动态卡片的增加/移除 */
|
||||
removeEventListener(
|
||||
type: FeedsCardsManagerEventType,
|
||||
callback: (event: CustomEvent<FeedsCard>) => void,
|
||||
options?: boolean | AddEventListenerOptions | undefined,
|
||||
): void {
|
||||
super.removeEventListener(type, callback, options)
|
||||
}
|
||||
/** 触发自定义事件 */
|
||||
dispatchCardEvent(type: FeedsCardsManagerEventType, card: FeedsCard) {
|
||||
const event = new CustomEvent(type, { detail: card })
|
||||
this.dispatchEvent(event)
|
||||
feedsCardCallbacks.forEach(c => c[type === FeedsCardsManagerEventType.AddCard ? 'added' : 'removed'](card))
|
||||
}
|
||||
/** 对当前页面开始监测 */
|
||||
async startWatching() {
|
||||
if (this.watching) {
|
||||
return true
|
||||
}
|
||||
this.watching = true
|
||||
const [adaptors] = registerAndGetData(ListAdaptorKey, [] as FeedsCardsListAdaptor[])
|
||||
const adaptor = adaptors.find(a => a.match.some(p => matchUrlPattern(p)))
|
||||
if (!adaptor) {
|
||||
console.warn('[FeedsCardsManager] No adaptor found', adaptors)
|
||||
return false
|
||||
}
|
||||
return adaptor.watchCardsList(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入元素添加动态卡片
|
||||
* @param node 动态卡片的元素
|
||||
*/
|
||||
abstract addCard(node: Node): Promise<void>
|
||||
/**
|
||||
* 根据传入元素移除动态卡片
|
||||
* @param node 动态卡片的元素
|
||||
*/
|
||||
abstract removeCard(node: Node): Promise<void>
|
||||
/**
|
||||
* 根据卡片列表元素更新动态卡片列表
|
||||
* @param cardsList 卡片列表元素
|
||||
*/
|
||||
abstract updateCards(cardsList: HTMLElement): readonly [MutationObserver, MutationObserverInit]
|
||||
}
|
||||
@ -1,66 +1,9 @@
|
||||
import { getCookieValue } from '@/core/utils'
|
||||
import { FeedsCard, FeedsCardCallback } from '../types'
|
||||
import { FeedsCardCallback } from '../types'
|
||||
import { feedsCardCallbacks } from './base'
|
||||
import { FeedsCardsManagerV1 } from './v1'
|
||||
import { FeedsCardsManagerV2 } from './v2'
|
||||
|
||||
const feedsCardCallbacks: Required<FeedsCardCallback>[] = []
|
||||
|
||||
/** 动态卡片管理器支持的自定义事件 */
|
||||
export const enum FeedsCardsManagerEventType {
|
||||
/** 新增动态卡片 */
|
||||
AddCard = 'addCard',
|
||||
/** 移除动态卡片 */
|
||||
RemoveCard = 'removeCard',
|
||||
}
|
||||
|
||||
/** 动态卡片管理器, 封装了动态卡片的解析与监测 */
|
||||
export abstract class FeedsCardsManager extends EventTarget {
|
||||
/** 是否已在监测中 */
|
||||
watching = false
|
||||
/** 监测到的动态卡片列表 */
|
||||
cards: FeedsCard[] = []
|
||||
/** 监听动态卡片的增加/移除 */
|
||||
addEventListener(
|
||||
type: FeedsCardsManagerEventType,
|
||||
listener: (event: CustomEvent<FeedsCard>) => void,
|
||||
options?: boolean | AddEventListenerOptions | undefined,
|
||||
): void {
|
||||
super.addEventListener(type, listener, options)
|
||||
}
|
||||
/** 取消监听动态卡片的增加/移除 */
|
||||
removeEventListener(
|
||||
type: FeedsCardsManagerEventType,
|
||||
callback: (event: CustomEvent<FeedsCard>) => void,
|
||||
options?: boolean | AddEventListenerOptions | undefined,
|
||||
): void {
|
||||
super.removeEventListener(type, callback, options)
|
||||
}
|
||||
/** 触发自定义事件 */
|
||||
dispatchCardEvent(type: FeedsCardsManagerEventType, card: FeedsCard) {
|
||||
const event = new CustomEvent(type, { detail: card })
|
||||
this.dispatchEvent(event)
|
||||
feedsCardCallbacks.forEach(c => c[type === FeedsCardsManagerEventType.AddCard ? 'added' : 'removed'](card))
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入元素添加动态卡片
|
||||
* @param node 动态卡片的元素
|
||||
*/
|
||||
abstract addCard(node: Node): Promise<void>
|
||||
/**
|
||||
* 根据传入元素移除动态卡片
|
||||
* @param node 动态卡片的元素
|
||||
*/
|
||||
abstract removeCard(node: Node): Promise<void>
|
||||
/**
|
||||
* 根据卡片列表元素更新动态卡片列表
|
||||
* @param cardsList 卡片列表元素
|
||||
*/
|
||||
abstract updateCards(cardsList: HTMLElement): readonly [MutationObserver, MutationObserverInit]
|
||||
/** 对当前页面开始监测 */
|
||||
abstract startWatching(): Promise<boolean>
|
||||
}
|
||||
|
||||
export const feedsCardsManager = (() => {
|
||||
const isV2Feeds = parseInt(getCookieValue('hit-dyn-v2')) > 0
|
||||
if (isV2Feeds) {
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import { childList } from '@/core/observer'
|
||||
import { sq } from '@/core/spin-query'
|
||||
import { matchUrlPattern } from '@/core/utils'
|
||||
import { registerAndGetData } from '@/plugins/data'
|
||||
import { FeedsCardsManager, FeedsCardsManagerEventType } from '.'
|
||||
import { FeedsCardsManager, FeedsCardsManagerEventType, getVueData } from './base'
|
||||
import { feedsCardTypes, FeedsCard, RepostFeedsCard, FeedsCardType } from '../types'
|
||||
import { FeedsCardsListAdaptor, ListAdaptorKey } from './adaptor'
|
||||
|
||||
const getFeedsCardType = (element: HTMLElement) => {
|
||||
if (element.querySelector('.repost')) {
|
||||
@ -45,8 +42,6 @@ const isRepostType = (card: FeedsCard): card is RepostFeedsCard => (
|
||||
* @param element 动态卡片的元素
|
||||
*/
|
||||
const parseCard = async (element: HTMLElement): Promise<FeedsCard> => {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const getVueData = (el: any) => el.__vue__ || el.parentElement.__vue__
|
||||
const getSimpleText = async (selector: string) => {
|
||||
const subElement = await sq(
|
||||
() => element.querySelector(selector),
|
||||
@ -234,17 +229,4 @@ export class FeedsCardsManagerV1 extends FeedsCardsManager {
|
||||
})
|
||||
})
|
||||
}
|
||||
async startWatching() {
|
||||
if (this.watching) {
|
||||
return true
|
||||
}
|
||||
this.watching = true
|
||||
const [adaptors] = registerAndGetData(ListAdaptorKey, [] as FeedsCardsListAdaptor[])
|
||||
const adaptor = adaptors.find(a => a.match.some(p => matchUrlPattern(p)))
|
||||
if (!adaptor) {
|
||||
console.warn('[FeedsCardsManager] No adaptor found', adaptors)
|
||||
return false
|
||||
}
|
||||
return adaptor.watchCardsList(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,77 @@
|
||||
import { FeedsCardsManager } from '.'
|
||||
import { childList } from '@/core/observer'
|
||||
import { descendingStringSort } from '@/core/utils/sort'
|
||||
import { createNodeValidator, FeedsCardsManager, FeedsCardsManagerEventType, getVueData } from './base'
|
||||
import { FeedsCard, feedsCardTypes } from '../types'
|
||||
|
||||
/** b 站的动态卡片 type 标记 -> FeedsCard.type */
|
||||
const feedsCardTypeMap = {
|
||||
DynamicTypeForward: feedsCardTypes.repost,
|
||||
DynamicTypeAv: feedsCardTypes.video,
|
||||
DynamicTypeDraw: feedsCardTypes.textWithImages,
|
||||
DynamicTypeWord: feedsCardTypes.text,
|
||||
DynamicTypePgc: feedsCardTypes.bangumi,
|
||||
DynamicTypeArticle: feedsCardTypes.column,
|
||||
DynamicTypeMusic: feedsCardTypes.audio,
|
||||
}
|
||||
const parseCard = async (element: HTMLElement): Promise<FeedsCard> => {
|
||||
const vueData = getVueData(element)
|
||||
const { modules, id_str, type } = vueData.data
|
||||
const { desc } = modules.module_dynamic
|
||||
const { name } = modules.module_author
|
||||
const { like, forward, comment } = modules.module_stat
|
||||
return {
|
||||
id: id_str,
|
||||
username: name,
|
||||
likes: like.count,
|
||||
reposts: forward.count,
|
||||
comments: comment.count,
|
||||
text: desc.text,
|
||||
type: feedsCardTypeMap[lodash.startCase(type)] ?? feedsCardTypeMap.DynamicTypeWord,
|
||||
element,
|
||||
get presented() { return element.parentNode !== null },
|
||||
async getText() {
|
||||
return getVueData(element).data.modules.module_dynamic.desc.text
|
||||
},
|
||||
}
|
||||
}
|
||||
const isNodeValid = createNodeValidator('bili-dyn-item')
|
||||
|
||||
/** 新版动态卡片管理器实现 */
|
||||
export class FeedsCardsManagerV2 extends FeedsCardsManager {
|
||||
|
||||
async addCard(node: Node) {
|
||||
if (!isNodeValid(node)) {
|
||||
return
|
||||
}
|
||||
const card = await parseCard(node)
|
||||
if (!card.presented) {
|
||||
return
|
||||
}
|
||||
this.cards.push(card)
|
||||
this.cards.sort(descendingStringSort(c => c.id))
|
||||
this.dispatchCardEvent(FeedsCardsManagerEventType.AddCard, card)
|
||||
}
|
||||
async removeCard(node: Node) {
|
||||
if (!isNodeValid(node)) {
|
||||
return
|
||||
}
|
||||
const vueData = getVueData(node)
|
||||
const id = vueData.data?.id_str ?? '0'
|
||||
const index = this.cards.findIndex(c => c.id === id)
|
||||
if (index === -1) {
|
||||
return
|
||||
}
|
||||
const [card] = this.cards.splice(index, 1)
|
||||
this.dispatchCardEvent(FeedsCardsManagerEventType.RemoveCard, card)
|
||||
}
|
||||
updateCards(cardsList: HTMLElement) {
|
||||
const selector = '.bili-dyn-item'
|
||||
const cards = dqa(cardsList, selector)
|
||||
cards.forEach(it => this.addCard(it))
|
||||
return childList(cardsList, records => {
|
||||
records.forEach(record => {
|
||||
record.addedNodes.forEach(node => this.addCard(node))
|
||||
record.removedNodes.forEach(node => this.removeCard(node))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,7 +245,7 @@ export const formData = (obj: Record<string, any>, config?: { encode?: boolean }
|
||||
/**
|
||||
* 移除一个数组中的元素
|
||||
* @param target 目标数组
|
||||
* @param property 数组元素判断
|
||||
* @param predicate 数组元素判断
|
||||
*/
|
||||
export const deleteValue = <ItemType> (
|
||||
target: ItemType[],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user