User:SunAfterRain/js/Vector2022StickyHeaderAutoCopy.js

注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。

/**
 * vector-2022-sticky-header-auto-copy
 * 將 vector-2022 中 #p-personal 的連結自動複製到 #p-personal-sticky-header
 * 複製後的連結之 id 會是 `${origId}-sticky-header`
 * 
 * 使用限制:
 * 1. 若是 Linster 不是用 jQuery.on 附上去的則無法複製
 *    可以用 `window.wgVector2022StickyHeaderAutoCopyUseNativeClickEvnet = true;` 讓點擊時直接調用 Element.click()
 * 2. 後續加入的連結若不是 mw.util.addPortletLink 附上去的可能無法掃描到
 *    可以用 `window.wgVector2022StickyHeaderAutoCopyUseMutationObserver = true;` 改用 MutationObserver,但瀏覽器負擔可能有點大
 * 3. 不處理人為移除的連結
 * 4. 已假定所有連結都有 id 且 id 都不會因為測試而重複,不會有與 css 選擇器不相容的 id,以及沒有人往 #p-personal-sticky-header
 *    注入沒有注入 #p-personal 的連結,否則複製後的連結定位可能會失敗
 * 
 * @author SunAfterRain
 */
// <nowiki>
$( () => {
	if ( mw.config.get( 'skin' ) !== 'vector-2022' ) {
		return;
	}
	
	const $personalTab = $( '#p-personal' );
	const $personalStickyHeaderTab = $( '#p-personal-sticky-header' );
	const stickHeaderIdSuffix = '-sticky-header';
	
	// 以下被列出的 id 會被無視
	const idBlackList = [
		'pt-userpage',
		'pt-sandbox',
		'pt-preferences',
		'pt-betafeatures',
		'pt-watchlist',
		'pt-mycontris',
		'pt-mentordashboard',
		'pt-logout',
	
		'cx-language', // CX
		'cx-imageGallery', // 上傳的檔案列表
		
		'utcdate', // Gadget-UTCLiveClock / 已有適配
	];
	
	function findLinkToCopy() {
		for ( const link of $personalTab.find( 'li' ).get() ) {
			applyCopy( link );
		}
	}
	
	function applyCopy( link ) {
		const $link = $( link );
		let id = $link.attr( 'id' ) || false;
		if ( id ) {
			if ( idBlackList.includes( id ) || !!$personalStickyHeaderTab.find( `#${ id }${ stickHeaderIdSuffix }` ).length ) {
				return;
			}
		}
		
		let $copyLink;
		if ( window.wgVector2022StickyHeaderAutoCopyUseNativeClickEvnet ) {
			const $a = $link.children( 'a' ).get();
			$copyLink = $link.clone();
			const $copyA = $copyLink.children( 'a' );
			for ( const [ index, $self ] of $copyLink.children( 'a' ).get().entries() ) {
				const $origAnchor = $a[ index ];
				$( $self ).on( 'click', ( e ) => {
				e.preventDefault();
					$origAnchor.click();
				} );
			}
		} else {
			$copyLink = $link.clone( false, true );
		}

		if ( id ) {
			$copyLink.attr( 'id', `${ id }${ stickHeaderIdSuffix }` );
		}

		const $next = $link.next();
		let insertNextNode = false;
		if ( $next.length && $next.attr( 'id' ) ) {
			// 例如 Gadget-UTCLiveClock 在兩處用了一樣的 id
			insertNextNode = $personalStickyHeaderTab.find( `#${ $next.attr( 'id' ) }, #${ $next.attr( 'id' ) }${ stickHeaderIdSuffix }` ).get( 0 );
		}
		if ( insertNextNode ) {
			$copyLink.before( insertNextNode );
		} else {
			$personalStickyHeaderTab.append( $copyLink );
		}
		
		mw.hook( 'util.addPortletLink' ).fire( $copyLink.get( 0 ), {
			id: id ? `${ id }${ stickHeaderIdSuffix }` : undefined,
			isInsertByAutoCopyScript: true
		} );
	}
	
	findLinkToCopy();
	
	if ( window.wgVector2022StickyHeaderAutoCopyUseMutationObserver ) {
		const personalTabList = $personalTab.find( 'ul' ).get( 0 );
		const observer = new MutationObserver( ( mutations ) => {
			for ( const mutation of mutations ) {
				if (
					mutation.target.tagName.toLowerCase() === 'li' &&
					mutation.target.parentNode === personalTabList
				) {
					applyCopy( item );
				}
			}
		} );

		observer.observe( personalTabList, {
			childList: true
		} );
	} else {
		mw.hook( 'util.addPortletLink' ).add( ( item, { id } ) => {
			if ( !$( item ).parents().get().includes( $personalTab.get( 0 ) ) ) {
				return;
			}

			applyCopy( item );
		} );
	}
	
	// 如果不在黑名單裡但自己有注入 #p-personal-sticky-header,把我們自己生成的刪掉
	mw.hook( 'util.addPortletLink' ).add( ( item, { id, isInsertByAutoCopyScript } ) => {
		if ( isInsertByAutoCopyScript || !$( item ).parents().get().includes( $personalStickyHeaderTab.get( 0 ) ) ) {
			return;
		}
		$personalStickyHeaderTab.find( 'li' ).filter( ( _, e ) => ( $( e ).attr( 'id' ) === id || $e.attr( 'id' ) === `${ id }${ stickHeaderIdSuffix }` ) && e !== item ).remove();
	} );
} );
// </nowiki>