diff --git a/src/components/feeds/api/manager/adaptor.ts b/src/components/feeds/api/manager/adaptor.ts index 9ef934ebd..c448cc7a2 100644 --- a/src/components/feeds/api/manager/adaptor.ts +++ b/src/components/feeds/api/manager/adaptor.ts @@ -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 } diff --git a/src/components/feeds/api/manager/base.ts b/src/components/feeds/api/manager/base.ts new file mode 100644 index 000000000..8ebc9c6f9 --- /dev/null +++ b/src/components/feeds/api/manager/base.ts @@ -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[] = [] + +// 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) => void, + options?: boolean | AddEventListenerOptions | undefined, + ): void { + super.addEventListener(type, listener, options) + } + /** 取消监听动态卡片的增加/移除 */ + removeEventListener( + type: FeedsCardsManagerEventType, + callback: (event: CustomEvent) => 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 + /** + * 根据传入元素移除动态卡片 + * @param node 动态卡片的元素 + */ + abstract removeCard(node: Node): Promise + /** + * 根据卡片列表元素更新动态卡片列表 + * @param cardsList 卡片列表元素 + */ + abstract updateCards(cardsList: HTMLElement): readonly [MutationObserver, MutationObserverInit] +} diff --git a/src/components/feeds/api/manager/index.ts b/src/components/feeds/api/manager/index.ts index fc2d98c34..0c9fba27b 100644 --- a/src/components/feeds/api/manager/index.ts +++ b/src/components/feeds/api/manager/index.ts @@ -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[] = [] - -/** 动态卡片管理器支持的自定义事件 */ -export const enum FeedsCardsManagerEventType { - /** 新增动态卡片 */ - AddCard = 'addCard', - /** 移除动态卡片 */ - RemoveCard = 'removeCard', -} - -/** 动态卡片管理器, 封装了动态卡片的解析与监测 */ -export abstract class FeedsCardsManager extends EventTarget { - /** 是否已在监测中 */ - watching = false - /** 监测到的动态卡片列表 */ - cards: FeedsCard[] = [] - /** 监听动态卡片的增加/移除 */ - addEventListener( - type: FeedsCardsManagerEventType, - listener: (event: CustomEvent) => void, - options?: boolean | AddEventListenerOptions | undefined, - ): void { - super.addEventListener(type, listener, options) - } - /** 取消监听动态卡片的增加/移除 */ - removeEventListener( - type: FeedsCardsManagerEventType, - callback: (event: CustomEvent) => 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 - /** - * 根据传入元素移除动态卡片 - * @param node 动态卡片的元素 - */ - abstract removeCard(node: Node): Promise - /** - * 根据卡片列表元素更新动态卡片列表 - * @param cardsList 卡片列表元素 - */ - abstract updateCards(cardsList: HTMLElement): readonly [MutationObserver, MutationObserverInit] - /** 对当前页面开始监测 */ - abstract startWatching(): Promise -} - export const feedsCardsManager = (() => { const isV2Feeds = parseInt(getCookieValue('hit-dyn-v2')) > 0 if (isV2Feeds) { diff --git a/src/components/feeds/api/manager/v1.ts b/src/components/feeds/api/manager/v1.ts index 879044ceb..e886fd43b 100644 --- a/src/components/feeds/api/manager/v1.ts +++ b/src/components/feeds/api/manager/v1.ts @@ -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 => { - // 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) - } } diff --git a/src/components/feeds/api/manager/v2.ts b/src/components/feeds/api/manager/v2.ts index 9bb6b3436..5ffcba2a7 100644 --- a/src/components/feeds/api/manager/v2.ts +++ b/src/components/feeds/api/manager/v2.ts @@ -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 => { + 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)) + }) + }) + } } diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 79a47a0a1..52be00690 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -245,7 +245,7 @@ export const formData = (obj: Record, config?: { encode?: boolean } /** * 移除一个数组中的元素 * @param target 目标数组 - * @param property 数组元素判断 + * @param predicate 数组元素判断 */ export const deleteValue = ( target: ItemType[],