/ src / entrypoints / channelDropdown.content / helpers / addBlockButtonToChannelDropdown.ts
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  };