addBlockButtonToChannelDropdown.ts
1 import type { createAddChannelBlockButton } from '@/js/createAddChannelBlockButton'; 2 import type { Logger } from '@/utils/logger'; 3 4 import type { ChannelDropdownAddedNode, ChannelDropdownAddedNodes } from '../types'; 5 6 const CHANNEL_FOLLOW_BUTTON_SELECTOR = 'div.channel-follow-button' as const; 7 const CHANNEL_ID_ATTR = 'data-channel-id' as const; 8 const CHANNEL_TITLE_SELECTOR = '.channel__title' as const; 9 const CHANNEL_LINK_SELECTOR = '.channel__title > a' as const; 10 11 export const addBlockButtonToChannelDropdown = ( 12 parentLogger: Logger, 13 addedNodes: ChannelDropdownAddedNodes, 14 addChannelBlockButton: ReturnType<typeof createAddChannelBlockButton>, 15 channelDropdownContent: Element, 16 channelId?: number, 17 ) => { 18 using logger = parentLogger.scopedGroupAuto('adding block button to channel dropdown'); 19 20 logger.debug( 21 'trying to get channel ID from attr', 22 CHANNEL_ID_ATTR, 23 'from node', 24 channelDropdownContent, 25 ); 26 27 if (typeof channelId !== 'number') { 28 const channelIdAttr = channelDropdownContent.getAttribute(CHANNEL_ID_ATTR); 29 const id = channelIdAttr && Number.parseInt(channelIdAttr, 10); 30 31 if (!Number.isInteger(id)) { 32 logger.warn('value', channelIdAttr, 'is not a valid channel ID'); 33 return; 34 } 35 36 channelId = id; 37 } 38 39 const nodeWithChannelTitle = channelDropdownContent.querySelector(CHANNEL_TITLE_SELECTOR); 40 41 if (!nodeWithChannelTitle) { 42 logger.info( 43 'there is no node with channel title found by selector', 44 CHANNEL_TITLE_SELECTOR, 45 'in node', 46 channelDropdownContent, 47 ); 48 return; 49 } 50 51 const channelTitle = 52 'innerText' in nodeWithChannelTitle && typeof nodeWithChannelTitle.innerText === 'string' 53 ? nodeWithChannelTitle.innerText 54 : nodeWithChannelTitle.textContent; 55 56 if (channelTitle === null) { 57 logger.warn( 58 '`innerText` of the channel title node', 59 nodeWithChannelTitle, 60 "isn't a string", 61 channelTitle, 62 ); 63 return; 64 } 65 66 let channelPermalink: string | undefined; 67 68 try { 69 const node = channelDropdownContent.querySelector(CHANNEL_LINK_SELECTOR); 70 if (node) { 71 channelPermalink = new URL(node.href).pathname.slice(1); 72 } else { 73 logger.warn( 74 'there is no node with channel link found by selector', 75 CHANNEL_LINK_SELECTOR, 76 'in node', 77 channelDropdownContent, 78 ); 79 } 80 } catch (err) { 81 logger.error('failed to get channel permalink:', err); 82 } 83 84 const channelFollowButton = channelDropdownContent.querySelector(CHANNEL_FOLLOW_BUTTON_SELECTOR); 85 86 if (!channelFollowButton) { 87 logger.warn( 88 '"Follow" button wrapper was not found with selector', 89 CHANNEL_FOLLOW_BUTTON_SELECTOR, 90 'in node', 91 channelDropdownContent, 92 ); 93 return; 94 } 95 96 const waivedWindow = window.wrappedJSObject || window; 97 const { WeakRef } = waivedWindow; 98 99 addChannelBlockButton({ 100 target: channelFollowButton, 101 channel: { 102 id: channelId, 103 title: channelTitle, 104 permalink: channelPermalink, 105 }, 106 onAdded(blockButton, unsubscribe) { 107 const entry = cloneInto([] as unknown as ChannelDropdownAddedNode, window); 108 addedNodes.push(entry); 109 110 entry[0] = new WeakRef(channelDropdownContent); 111 entry[1] = new WeakRef(blockButton); 112 entry[2] = new WeakRef( 113 exportFunction(() => { 114 parentLogger.debug('removing channel', channelId, 'listener'); 115 unsubscribe(); 116 }, waivedWindow), 117 ); 118 }, 119 }); 120 };