跳转到内容

User:SunAfterRain/js/InternalLinkHelperV1.js

维基百科,自由的百科全书
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
警告:User:SunAfterRain/js下的所有script幾乎都是本人隨興撰寫的,不保證可用性。如要使用,請確保您的瀏覽器支援ES6、async & await等語法糖,並接受本人突然改變任何內容以至於用法改變或是毀損無法使用。任何問題請至:User talk:SunAfterRain
/**
 * InternalLinkHelperV1
 * 復刻舊版 InternalLinkHelper 小工具
 * 
 * 使用方法:
 * window.wgInternalLinkHelperConfig = { ... }; // 可選,選項請參考下方的 Config 之 jsdoc
 * importScript( 'User:SunAfterRain/js/InternalLinkHelperV1.js' ); // backlink: [[User:SunAfterRain/js/InternalLinkHelperV1.js]]
 * 
 * 舊式用法(已棄用):
 * window.wgInternalLinkHelperMode = 'redonly'; // 或是 redtipsy、redplain、external、suffix、cravix、altcolor、ilbluehl
 * importScript( 'User:SunAfterRain/js/InternalLinkHelperV1.js' ); // backlink: [[User:SunAfterRain/js/InternalLinkHelperV1.js]]
 * 
 * @author SunAfterRain
 * @license CC-BY-SA 4.0
 * 
 * 備註:
 * 1. jquery.tipsy 和 MediaWiki:Tooltips.js 已被替換成 OO.ui.PopupWidget,所以樣式和行為模式可能略有不同
 * 2. 怪 bug 請隨意反饋,但除非是不屬於 1 引入的問題並且原版無法出現,不然我大概不會修
 */
/* _addText: {{User:SunAfterRain/js}} */
/* global $ */
mw.loader.load('/w/index.php?title=User:SunAfterRain/js/InternalLinkHelperV1.css&action=raw&ctype=text/css', 'text/css');
Promise.all([
	$.ready,
	mw.loader.using(['mediawiki.util', 'oojs-ui-core', 'ext.gadget.HanAssist'])
]).then(async ([_, require]) => {
	const HanAssist = require('ext.gadget.HanAssist');

	/**
	 * @typedef {Object} Config
	 * @property {'only-red' | 'red-plain' | 'external' | 'tooltip'} mode
	 * * only-red: 僅顯示紅色連結
	 * * red-plain: 顯示紅色連結和無連結的原文
	 * * external: 只顯示到外文維基的連結
	 * * tooltip: 顯示彈窗
	 * @property {boolean} [displaySuffix=false] 僅在 mode=external 時有效,顯示語言名字後綴
	 * @property {boolean} [greenLink=false] 僅在 mode=tooltip 時有效,顯示綠色連結
	 * @property {boolean} [clickTrigger=false] 僅在 mode=tooltip 時有效,Tooltip 改以點擊連結時顯示
	 * @property {boolean} [highlightExisting=false] 僅在 mode=tooltip 時有效,對於已存在頁面的情況下高亮表示
	   */
	/** @type {Record<string, Config>} */
	const gadgetModeMapConfig = {
		// redonly - 只顯示紅鏈
		redonly: {
			mode: 'only-red',
		},
		// redtipsy - 在 Tooltip 中顯示原文連結
		redtipsy: {
			mode: 'tooltip'
		},
		// redplain - 顯示紅色連結和無連結的原文
		redplain: {
			mode: 'red-plain',
		},
		// external - 直接指向原文
		external: {
			mode: 'external',
		},
		// suffix - 繼承 external,指向原文和語言名後綴
		suffix: {
			mode: 'external',
			displaySuffix: true,
		},
		// cravix - 與 redtipsy 類似,但連結顏色是綠色並且 Tooltip 是點擊連結時打開
		cravix: {
			mode: 'tooltip',
			greenLink: true,
			clickTrigger: true,
		},
		// altcolor - 繼承 redtipsy,只是連結顏色換了
		altcolor: {
			mode: 'tooltip',
			greenLink: true,
		},
		// ilbluehl - 繼承 altcolor,對於已存在頁面的情況下高亮表示
		ilbluehl: {
			mode: 'tooltip',
			greenLink: true,
			highlightExisting: true,
		},
	};

	const notifyMessage = HanAssist.convByVar({
		hans: '条目“$1”尚未创建,可参考$2维基百科的对应页面:',
		hant: '條目「$1」尚未創建,可參考$2維基百科的對應頁面:'
	});

	let $popupContainor;
	function getContainor() {
		if (!$popupContainor) {
			$popupContainor = $('<div>').attr({
				class: 'userjs-InternalLinkHelperV1-container'
			});
			$popupContainor.appendTo(document.body);
		}
		return $popupContainor;
	}
	function clearContainor() {
		$popupContainor?.empty();
	}
	function buildTooltip($self, allowCloseByUser = false) {
		const origTitle = $self.data('orig-title'),
			$foreignSpan = $self.find('.ilh-link'),
			$linkAnchor = $self.find('.ilh-page a'),
			$langSpan = $self.find('.ilh-lang'),
			langName = $langSpan.text();

		if (!$linkAnchor.length) {
			return false;
		}

		const popupWidget = new OO.ui.PopupWidget({
			$content: $('<div>')
				.append(
					document.createTextNode(
						mw.format(notifyMessage, origTitle, langName)
					),
					$foreignSpan.clone()
				),
			padded: true,
			head: allowCloseByUser,
			anchor: false,
			align: 'center',
			autoFlip: false,
			autoclose: false,
			$floatableContainer: $linkAnchor
		});
		popupWidget.$element.appendTo(getContainor());

		return { popupWidget, $linkAnchor };
	}

	let config;
	if ('wgInternalLinkHelperConfig' in window) {
		config = window.wgInternalLinkHelperConfig;
	} else if ('wgInternalLinkHelperMode' in window) {
		const mode = window.wgInternalLinkHelperMode?.toLowerCase();
		config = gadgetModeMapConfig[mode];
		if (!config) {
			throw new Error('InternalLinkHelperV1 gadget mode is empty or invalid.');
		}
	} else {
		config = gadgetModeMapConfig.ilbluehl;
	}

	function addClass(className) {
		document.documentElement.classList.add(`userjs-ilhv1-${className}`);
	}

	let hook = false;
	switch (config.mode) {
		case 'only-red':
			addClass('no-comment');
			break;

		case 'red-plain':
			addClass('red-plain');
			hook = ($content) => {
				for (const self of $content.find('.ilh-link')) {
					$(self).text($(self).text()); // remove link
				}
			};
			break;

		case 'external':
			addClass('external');
			if (config.displaySuffix) {
				addClass('suffix');
			}

			hook = ($content) => {
				for (const self of $content.find('.ilh-link')) {
					const $extra = $(self).find('.ilh-link a');
					if ($extra.length == 0) {
						continue;
					}
					$(self).find('.ilh-page a')
						.removeClass('new')
						.addClass('extiw')
						.attr('href', $extra.attr('href'))
						.attr('title', $extra.attr('title'));
				}
			};
			break;

		case 'tooltip':
			addClass('no-comment');
			if (config.greenLink) {
				addClass('green-link');
			}
			if (config.highlightExisting) {
				addClass('highlight-existing');
			}

			if (config.clickTrigger) {
				hook = ($content) => {
					const ilhList = $content.find('.ilh-all:not(.ilh-blue)').get();
					if (!ilhList.length) {
						return;
					}
					clearContainor();
					for (const self of ilhList) {
						const $self = $(self);

						const { popupWidget, $linkAnchor } = buildTooltip($self, true);
						if (!popupWidget) {
							continue;
						}

						$linkAnchor.on('click', (ev) => {
							ev.preventDefault();
							$(ilhList.filter(element => !$self.is(element))).trigger('internalLinkHelper-close');
							popupWidget.toggle(true);
						});
						$self.on('internalLinkHelper-close', () => {
							popupWidget.toggle(false);
						});
					}
				};
			} else {
				hook = ($content) => {
					const ilhList = $content.find('.ilh-all:not(.ilh-blue)').get();
					if (!ilhList.length) {
						return;
					}
					clearContainor();
					for (const self of ilhList) {
						const $self = $(self);

						const { popupWidget, $linkAnchor } = buildTooltip($self);
						if (!popupWidget) {
							continue;
						}

						const { maybeClearTimeout, mouseenter, mouseleave } = (() => {
							let timeout = null;
							const maybeClearTimeout = () => {
								if (timeout !== null) {
									clearTimeout(timeout);
								}
							};
							const autoSetTimeout = (...args) => {
								maybeClearTimeout();
								timeout = setTimeout(...args);
							};
							const timeoutFunction = () => {
								popupWidget.toggle(false);
								timeout = null;
							};
							return {
								maybeClearTimeout,
								mouseenter() {
									if (popupWidget.isVisible()) {
										maybeClearTimeout();
									} else {
										$(ilhList.filter(element => !$self.is(element))).trigger('internalLinkHelper-close');
										popupWidget.toggle(true);
									}
								},
								mouseleave() {
									autoSetTimeout(() => {
										popupWidget.toggle(false);
									}, 500);
								}
							};
						})();
						$linkAnchor.add(popupWidget.$popup).on('mouseenter', mouseenter).on('mouseleave', mouseleave);
						$self.on('internalLinkHelper-close', () => {
							maybeClearTimeout();
							popupWidget.toggle(false);
						});
					}
				};
			}
			break;
		default:
			throw new Error('InternalLinkHelperV1 mode is empty or invalid.');
	}

	if (hook) {
		mw.hook('wikipage.content').add(hook);
	}
});