User:Diskdance/VariantAlly-prod.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
/**!
* _________________________________________________________________________________
* | |
* | === WARNING: GLOBAL GADGET FILE === |
* | Changes to this page affect many users. |
* | Please discuss changes on the talk page, [[WP:VPT]] or GitHub before editing. |
* |_________________________________________________________________________________|
*
* Built from GitHub repository (https://github.com/wikimedia-gadgets/VariantAlly), you should not make
* changes directly here.
*
* See https://github.com/diskdance/wikimedia-gadgets/blob/main/CONTRIBUTING.md for build instructions.
*/
// <nowiki>
'use strict';
// Including:
// - w.wiki (preserve short link destination)
const BLOCKED_REFERRER_HOST = /^w\.wiki$/i;
function isLoggedIn() {
return mw.config.exists('wgUserId');
}
/**
* Check whether referrer originates from the same domain.
*/
function isReferrerSelf() {
try {
return new URL(document.referrer).hostname === location.hostname;
}
catch (_a) {
// Invalid URL
return false;
}
}
function isReferrerBlocked() {
try {
return BLOCKED_REFERRER_HOST.test(new URL(document.referrer).hostname);
}
catch (_a) {
// Invalid URL
return false;
}
}
function isViewingPage() {
return mw.config.get('wgAction') === 'view';
}
/**
* Check whether the current language (set in user preference or by ?uselang=xxx)
* is Chinese or not.
*/
function isLangChinese() {
return mw.config.get('wgUserLanguage').startsWith('zh');
}
function isWikitextPage() {
return mw.config.get('wgCanonicalNamespace') !== 'Special'
&& mw.config.get('wgPageContentModel') === 'wikitext';
}
const LOCAL_VARIANT_KEY = 'va-var';
const OPTOUT_KEY = 'va-optout';
const VALID_VARIANTS = [
'zh-cn',
'zh-sg',
'zh-my',
'zh-tw',
'zh-hk',
'zh-mo',
];
const VARIANTS = [
'zh',
'zh-hans',
'zh-hant',
...VALID_VARIANTS,
];
const EXT_VARIANTS = [
'zh-hans-cn',
'zh-hans-sg',
'zh-hans-my',
'zh-hant-tw',
'zh-hant-hk',
'zh-hant-mo',
...VARIANTS,
];
// Some browsers (e.g. Firefox Android) may return such languages
const EXT_MAPPING = {
'zh-hans-cn': 'zh-cn',
'zh-hans-sg': 'zh-sg',
'zh-hans-my': 'zh-my',
'zh-hant-tw': 'zh-tw',
'zh-hant-hk': 'zh-hk',
'zh-hant-mo': 'zh-mo',
};
function isVariant(str) {
return VARIANTS.includes(str);
}
function isValidVariant(str) {
return VALID_VARIANTS.includes(str);
}
function isExtVariant(str) {
return EXT_VARIANTS.includes(str);
}
/**
* Maps additional lang codes to standard variants.
*
* @returns standard variant
*/
function normalizeVariant(extVariant) {
var _a;
return (_a = EXT_MAPPING[extVariant]) !== null && _a !== void 0 ? _a : extVariant;
}
/**
* Get current variant of the page (don't be misled by config naming).
* @returns variant, null for non-wikitext page (but NOT all such pages returns null!)
*/
function getPageVariant() {
const result = mw.config.get('wgUserVariant');
return result !== null && isExtVariant(result) ? normalizeVariant(result) : null;
}
/**
* Get account variant.
* @returns account variant, null for anonymous user
*/
function getAccountVariant() {
if (isLoggedIn()) {
const result = mw.user.options.get('variant');
return isExtVariant(result) ? normalizeVariant(result) : null;
}
return null;
}
function getLocalVariant() {
const result = localStorage.getItem(LOCAL_VARIANT_KEY);
if (result === null || !isExtVariant(result)) {
return null;
}
return normalizeVariant(result);
}
/**
* Return browser language if it's a Chinese variant.
* @returns browser variant
*/
function getBrowserVariant() {
var _a;
return (_a = navigator.languages
.map((lang) => lang.toLowerCase())
.filter(isExtVariant)
.map(normalizeVariant)
.find(isVariant)) !== null && _a !== void 0 ? _a : null;
}
/**
* Get the "natural" variant inferred by MediaWiki for anonymous users
* when the link doesn't specify a variant.
*
* Used in link normalization.
*
* FIXME: Old Safari is known to break this method.
* User reported that on an iOS 14 device with Chinese language and Singapore region settings,
* Accept-Language is zh-cn (thus inferred by MediaWiki) but this method returns zh-sg.
*
* @returns variant
*/
function getMediaWikiVariant() {
var _a;
return (_a = getAccountVariant()) !== null && _a !== void 0 ? _a : getBrowserVariant();
}
/**
* Calculate preferred variant from browser variant, local variant and account variant.
*
* Priority: account variant > browser variant > local variant
*
* @returns preferred variant
*/
function calculatePreferredVariant() {
return [getAccountVariant(), getBrowserVariant(), getLocalVariant()]
.map((variant) => variant !== null && isValidVariant(variant) ? variant : null)
.reduce((prev, curr) => prev !== null && prev !== void 0 ? prev : curr);
}
function setLocalVariant(variant) {
localStorage.setItem(LOCAL_VARIANT_KEY, variant);
}
function setOptOut() {
localStorage.setItem(OPTOUT_KEY, '');
}
function isOptOuted() {
return localStorage.getItem(OPTOUT_KEY) !== null;
}
const REGEX_WIKI_URL = /^\/(?:wiki|zh(?:-\w+)?)\//i;
const REGEX_VARIANT_URL = /^\/zh(?:-\w+)?\//i;
const VARIANT_PARAM = 'va-variant';
function isEligibleForRewriting(link) {
try {
// No rewriting for empty links
if (link === '') {
return false;
}
const url = new URL(link, location.origin);
// No rewriting if link itself has variant info
if (REGEX_VARIANT_URL.test(url.pathname)) {
return false;
}
if (url.searchParams.has('variant')) {
return false;
}
// No rewriting for foreign origin URLs
// Note that links like javascript:void(0) are blocked by this
if (url.host !== location.host) {
return false;
}
return true;
}
catch (_a) {
return false;
}
}
function rewriteLink(link, variant) {
try {
const normalizationTargetVariant = getMediaWikiVariant();
const url = new URL(link, location.origin);
const pathname = url.pathname;
const searchParams = url.searchParams;
if (REGEX_WIKI_URL.test(pathname)) {
url.pathname = `/${variant}/${url.pathname.replace(REGEX_WIKI_URL, '')}`;
searchParams.delete('variant'); // For things like /zh-cn/A?variant=zh-hk
}
else {
searchParams.set('variant', variant);
}
if (variant === normalizationTargetVariant) {
// Normalize the link.
//
// For example, for link /zh-tw/Title and normalization variant zh-tw, the result is /wiki/Title,
// while for the same link and normalization variant zh-cn, the result is /zh-tw/Title (unchanged).
url.pathname = url.pathname.replace(REGEX_WIKI_URL, '/wiki/');
url.searchParams.delete('variant');
}
const result = url.toString();
return result;
}
catch (_a) {
return link;
}
}
function redirect(preferredVariant, options = {}) {
var _a;
const origLink = (_a = options.link) !== null && _a !== void 0 ? _a : location.href;
const newLink = rewriteLink(origLink, preferredVariant);
// Prevent infinite redirects
// This could happen occasionally, see getMediaWikiVariant()'s comments
if (options.forced || newLink !== location.href) {
// Use replace() to prevent navigating back
location.replace(newLink);
}
}
function checkThisPage(preferredVariant, pageVariant) {
if (pageVariant === preferredVariant) {
return;
}
const redirectionOrigin = mw.config.get('wgRedirectedFrom');
if (redirectionOrigin === null) {
redirect(preferredVariant);
return;
}
// If current page is redirected from another page, rewrite link to point to
// the original redirect so the "redirected from XXX" hint is correctly displayed
// Use URL to reserve other parts of the link
const redirectionURL = new URL(location.href);
redirectionURL.pathname = `/wiki/${redirectionOrigin}`;
redirect(preferredVariant, { link: redirectionURL.toString() });
}
function rewriteAnchors(variant) {
['click', 'auxclick', 'dragstart'].forEach((name) => {
document.addEventListener(name, (ev) => {
const target = ev.target;
if (target instanceof Element) {
// Do not write <a> with hash only href or no href
// which is known to cause breakage in e.g. Visual Editor
const anchor = target.closest('a[href]:not([href^="#"])');
if (anchor !== null) {
const origLink = anchor.href;
if (!isEligibleForRewriting(origLink)) {
return;
}
const newLink = rewriteLink(origLink, variant);
if (newLink === origLink) {
return;
}
// Browser support: Safari < 14
// Fail silently when DragEvent is not present
if (window.DragEvent && ev instanceof DragEvent && ev.dataTransfer) {
// Modify drag data directly because setting href has no effect in drag event
ev.dataTransfer.types.forEach((type) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ev.dataTransfer.setData(type, newLink);
});
}
else {
// Use a mutex to avoid being overwritten by overlapped handler calls
if (anchor.dataset.vaMutex === undefined) {
anchor.dataset.vaMutex = '';
}
anchor.href = newLink;
// HACK: workaround popups not working on modified links
// Add handler to <a> directly so it was triggered before anything else
['mouseover', 'mouseleave', 'keyup'].forEach((innerName) => {
anchor.addEventListener(innerName, (innerEv) => {
if (anchor.dataset.vaMutex !== undefined) {
anchor.href = origLink;
delete anchor.dataset.vaMutex;
}
}, { once: true });
});
}
}
}
});
});
}
function showVariantPrompt() {
mw.loader.using('ext.gadget.VariantAllyDialog').then(function(require){return require("ext.gadget.VariantAllyDialog")});
}
/**
* Set local variant according to URL query parameters.
*
* e.g. a URL with ?va-variant=zh-cn will set local variant to zh-cn
*/
function applyURLVariant() {
const variant = new URL(location.href).searchParams.get(VARIANT_PARAM);
if (variant !== null && isValidVariant(variant)) {
setLocalVariant(variant);
}
}
/**
* Collect metrics, visible at grafana.wikimedia.org
*
* @param name metric name
*/
function stat(name) {
mw.track(`counter.gadget_VariantAlly.${name}`);
}
function main() {
// Manually opt outed users
if (isOptOuted()) {
return;
}
if (isLoggedIn()) {
return;
}
// Non-Chinese pages/users
if (!isLangChinese()) {
return;
}
applyURLVariant();
const preferredVariant = calculatePreferredVariant();
if (preferredVariant !== null) {
// Optimistically set local variant to be equal to browser variant
// In case the user's browser language becomes invalid in the future,
// this reduces the possibility to show prompt to disrupt users.
setLocalVariant(preferredVariant);
}
const pageVariant = getPageVariant();
// Non-article page (JS/CSS pages, Special pages etc.)
if (pageVariant === null || !isWikitextPage()) {
// Such page can't have variant, but preferred variant may be available
// So still rewrite links
if (preferredVariant !== null) {
rewriteAnchors(preferredVariant);
}
return;
}
// Preferred variant unavailable
if (preferredVariant === null) {
if (isViewingPage()) {
showVariantPrompt();
return;
}
return;
}
if (isReferrerBlocked()) {
rewriteAnchors(preferredVariant);
return;
}
// On-site navigation to links ineligible for writing
// The eligibility check is require because user may click on a link with variant intentionally
// e.g. variant dropdown and {{Variant-cnhktwsg}}
if (isReferrerSelf() && !isEligibleForRewriting(location.href)) {
rewriteAnchors(preferredVariant);
return;
}
checkThisPage(preferredVariant, pageVariant);
rewriteAnchors(preferredVariant);
}
main();
exports.redirect = redirect;
exports.setLocalVariant = setLocalVariant;
exports.setOptOut = setOptOut;
exports.stat = stat;
// </nowiki>