MediaWiki:Gadget-PagePreviews.js
Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.
- Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
- Konqueror and Chrome: click Reload or press F5;
- Opera: clear the cache in Tools → Preferences;
- Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.
- This script lacks a documentation subpage. Please create it.
- This script is a part of the
PagePreviews
gadget (edit definitions)- Description (edit): Show a preview when hovering over a link to an entry. on by default
- Useful links: subpage list • links • redirects
// <nowiki>
function pagePreviews() {
const animationSpeed = 0.2; // seconds
const width = 320; // px
const height = 210; // px, including triangle height
const WP_PREFIXES = {'acronym':0,'advisory':0,'aew':0,'antwiki':0,'appropedia':0,'aquariumwiki':0,'arborwiki':0,'arxiv':0,'baden':0,'battlestarwiki':0,'bcnbio':0,'beacha':0,'betawiki':0,'betawikiversity':0,'bibcode':0,'bibliowiki':0,'botwiki':0,'boxrec':0,'bugzilla':0,'bulba':0,'c2':0,'cache':0,'centralwikia':0,'choralwiki':0,'citizendium':0,'commons':0,'communityscheme':0,'communitywiki':0,'comune':0,'creativecommons':0,'creativecommonswiki':0,'dbdump':0,'dcdatabase':0,'debian':0,'devmo':0,'dico':0,'dicoado':0,'dict':0,'dictionary':0,'diffblog':0,'discord':0,'disinfopedia':0,'dmoz':0,'dmozs':0,'doi':0,'donate':0,'doom_wiki':0,'download':0,'dpd':0,'dpla':0,'drae':0,'elibre':0,'emacswiki':0,'encyc':0,'englyphwiki':0,'enkol':0,'esolang':0,'etherpad':0,'ethnologue':0,'ethnologuefamily':0,'exkcd':0,'exotica':0,'fanimutationwiki':0,'fedora':0,'finalfantasy':0,'finnix':0,'flickrphoto':0,'flickruser':0,'foldoc':0,'foundation':0,'foundationsite':0,'freebsdman':0,'freedomdefined':0,'freenode':0,'freesoft':0,'gardenology':0,'gentoo':0,'genwiki':0,'gerrit':0,'git':0,'gitlab':0,'globalcontribs':0,'glottolog':0,'glottopedia':0,'google':0,'googledefine':0,'googlegroups':0,'gucprefix':0,'guildwarswiki':0,'gutenberg':0,'gutenbergwiki':0,'hackerspaces':0,'hammondwiki':0,'hdl':0,'heraldik':0,'horizonlabs':0,'hrfwiki':0,'hrwiki':0,'iarchive':0,'imdbcompany':0,'imdbname':0,'imdbtitle':0,'incubator':0,'infosphere':0,'irc':0,'ircrc':0,'ircs':0,'isni':0,'iso639-3':0,'issn':0,'iuridictum':0,'jaglyphwiki':0,'jira':0,'jstor':0,'kamelo':0,'karlsruhe':0,'komicawiki':0,'labsconsole':0,'lexemes':0,'liberachat':0,'libreplanet':0,'lingualibre':0,'linguistlist':0,'listarchive':0,'livepedia':0,'localwiki':0,'lofc':0,'lojban':0,'lokalhistoriewiki':0,'lostpedia':0,'luxo':0,'mail':0,'mailarchive':0,'mariowiki':0,'marveldatabase':0,'mdwiki':0,'meatball':0,'mediawikiwiki':0,'mediazilla':0,'memoryalpha':0,'metawiki':0,'metawikimedia':0,'metawikipedia':0,'mineralienatlas':0,'mixnmatch':0,'moinmoin':0,'mosapedia':0,'mozillawiki':0,'mozillazinekb':0,'mw':0,'mwod':0,'mwot':0,'nara':0,'nlab':0,'nost':'nostalgia','nostalgia':'nostalgia','oclc':0,'oeis':0,'oldwikisource':0,'olpc':0,'omegawiki':0,'openlibrary':0,'openstreetmap':0,'openwetware':0,'organicdesign':0,'orthodoxwiki':0,'osmwiki':0,'otrs':0,'otrswiki':0,'outreach':0,'outreachwiki':0,'owasp':0,'paws':0,'petscan':0,'phab':0,'phabricator':0,'planetmath':0,'pmid':0,'pokewiki':0,'pokéwiki':0,'policy':0,'proofwiki':0,'pyrev':0,'pythoninfo':0,'quality':0,'quarry':0,'rcirc':0,'regiowiki':0,'rev':0,'revo':0,'rfc':0,'rheinneckar':0,'rodovid':0,'rt':0,'scholar':0,'schoolswp':0,'scores':0,'scoutwiki':0,'scramble':0,'securewikidc':0,'semantic-mw':0,'senseislibrary':0,'sep11':0,'sharemap':0,'silcode':0,'slashdot':0,'sourceforge':0,'spcom':0,'species':0,'stats':0,'stewardry':0,'strategy':0,'strategywiki':0,'sulutil':0,'svn':0,'swtrain':0,'tardis':0,'tclerswiki':0,'tenwiki':'ten','test2wiki':'test2','testwiki':'test','testwikidata':0,'tfwiki':0,'thelemapedia':0,'theopedia':0,'ticket':0,'tmbw':0,'toolforge':0,'toolhub':0,'toollabs':0,'tools':0,'translatewiki':0,'tswiki':0,'tviv':0,'twiki':0,'twl':0,'tyvawiki':0,'umap':0,'uncyclopedia':0,'unihan':0,'urbandict':0,'usability':0,'usemod':0,'utrs':0,'viaf':0,'vikidia':0,'vlos':0,'votewiki':0,'vrts':0,'vrtwiki':0,'wcna':0,'weirdgloop':0,'werelate':0,'wg':'wg-en','wikia':0,'wikiapiary':0,'wikiasite':0,'wikibooks':0,'wikicities':0,'wikicity':0,'wikiconference':0,'wikidata':0,'wikiedudashboard':0,'wikifunctions':0,'wikifur':0,'wikihow':0,'wikiindex':0,'wikilivres':0,'wikilivresru':0,'wikimania':0,'wikimedia':0,'wikinews':0,'wikinfo':0,'wikinvest':0,'wikipapers':0,'wikipedia':'en','wikipediawikipedia':0,'wikiquote':0,'wikiskripta':0,'wikisophia':0,'wikisource':0,'wikisp':0,'wikispecies':0,'wikispore':0,'wikispot':0,'wikitech':0,'wikitrek':0,'wikiti':0,'wikiversity':0,'wikivoyage':0,'wikiwikiweb':0,'wiktionary':0,'wm2005':0,'wm2006':0,'wm2007':0,'wm2008':0,'wm2009':0,'wm2010':0,'wm2011':0,'wm2012':0,'wm2013':0,'wm2014':0,'wm2015':0,'wm2016':0,'wm2017':0,'wm2018':0,'wmam':0,'wmania':0,'wmar':0,'wmat':0,'wmau':0,'wmbd':0,'wmbe':0,'wmbr':0,'wmca':0,'wmch':0,'wmcl':0,'wmcn':0,'wmco':0,'wmcz':0,'wmcz_docs':0,'wmcz_old':0,'wmdc':0,'wmde':0,'wmdeblog':0,'wmdk':0,'wmdoc':0,'wmec':0,'wmee':0,'wmes':0,'wmet':0,'wmf':0,'wmfblog':0,'wmfdashboard':0,'wmfi':0,'wmfr':0,'wmge':0,'wmhi':0,'wmhk':0,'wmhu':0,'wmid':0,'wmil':0,'wmin':0,'wmit':0,'wmke':0,'wmmk':0,'wmmx':0,'wmnl':0,'wmno':0,'wmnyc':0,'wmpa-us':0,'wmph':0,'wmpl':0,'wmplsite':0,'wmpt':0,'wmpunjabi':0,'wmromd':0,'wmrs':0,'wmru':0,'wmse':0,'wmsk':0,'wmteam':0,'wmtr':0,'wmtw':0,'wmua':0,'wmuk':0,'wmve':0,'wmza':0,'wookieepedia':0,'wowwiki':0,'wurmpedia':0,'xkcd':0,'xtools':0,'zum':0,'c':0,'m':0,'meta':0,'d':0,'f':0,'aa':'aa','ab':'ab','ace':'ace','ady':'ady','af':'af','ak':'ak','als':'als','alt':'alt','am':'am','ami':'ami','an':'an','ang':'ang','anp':'anp','ar':'ar','arc':'arc','ary':'ary','arz':'arz','as':'as','ast':'ast','atj':'atj','av':'av','avk':'avk','awa':'awa','ay':'ay','az':'az','azb':'azb','ba':'ba','ban':'ban','bar':'bar','bat-smg':'bat-smg','bbc':'bbc','bcl':'bcl','be':'be','be-tarask':'be-tarask','be-x-old':'be-tarask','bew':'bew','bg':'bg','bh':'bh','bi':'bi','bjn':'bjn','blk':'blk','bm':'bm','bn':'bn','bo':'bo','bpy':'bpy','br':'br','bs':'bs','btm':'btm','bug':'bug','bxr':'bxr','ca':'ca','cbk-zam':'cbk-zam','cdo':'cdo','ce':'ce','ceb':'ceb','ch':'ch','cho':'cho','chr':'chr','chy':'chy','ckb':'ckb','co':'co','cr':'cr','crh':'crh','cs':'cs','csb':'csb','cu':'cu','cv':'cv','cy':'cy','da':'da','dag':'dag','de':'de','dga':'dga','din':'din','diq':'diq','dsb':'dsb','dtp':'dtp','dty':'dty','dv':'dv','dz':'dz','ee':'ee','el':'el','eml':'eml','en':'en','eo':'eo','es':'es','et':'et','eu':'eu','ext':'ext','fa':'fa','fat':'fat','ff':'ff','fi':'fi','fiu-vro':'fiu-vro','fj':'fj','fo':'fo','fon':'fon','fr':'fr','frp':'frp','frr':'frr','fur':'fur','fy':'fy','ga':'ga','gag':'gag','gan':'gan','gcr':'gcr','gd':'gd','gl':'gl','glk':'glk','gn':'gn','gom':'gom','gor':'gor','got':'got','gpe':'gpe','gsw':'als','gu':'gu','guc':'guc','gur':'gur','guw':'guw','gv':'gv','ha':'ha','hak':'hak','haw':'haw','he':'he','hi':'hi','hif':'hif','ho':'ho','hr':'hr','hsb':'hsb','ht':'ht','hu':'hu','hy':'hy','hyw':'hyw','hz':'hz','ia':'ia','id':'id','ie':'ie','ig':'ig','igl':'igl','ii':'ii','ik':'ik','ilo':'ilo','inh':'inh','io':'io','is':'is','it':'it','iu':'iu','ja':'ja','jam':'jam','jbo':'jbo','jv':'jv','ka':'ka','kaa':'kaa','kab':'kab','kbd':'kbd','kbp':'kbp','kcg':'kcg','kg':'kg','ki':'ki','kj':'kj','kk':'kk','kl':'kl','km':'km','kn':'kn','ko':'ko','koi':'koi','kr':'kr','krc':'krc','ks':'ks','ksh':'ksh','ku':'ku','kus':'kus','kv':'kv','kw':'kw','ky':'ky','la':'la','lad':'lad','lb':'lb','lbe':'lbe','lez':'lez','lfn':'lfn','lg':'lg','li':'li','lij':'lij','lld':'lld','lmo':'lmo','ln':'ln','lo':'lo','lrc':'lrc','lt':'lt','ltg':'ltg','lv':'lv','lzh':'zh-classical','mad':'mad','mai':'mai','map-bms':'map-bms','mdf':'mdf','mg':'mg','mh':'mh','mhr':'mhr','mi':'mi','min':'min','mk':'mk','ml':'ml','mn':'mn','mni':'mni','mnw':'mnw','mo':'mo','mr':'mr','mrj':'mrj','ms':'ms','mt':'mt','mus':'mus','mwl':'mwl','my':'my','myv':'myv','mzn':'mzn','na':'na','nah':'nah','nan':'zh-min-nan','nap':'nap','nds':'nds','nds-nl':'nds-nl','ne':'ne','new':'new','ng':'ng','nia':'nia','nl':'nl','nn':'nn','no':'no','nov':'nov','nqo':'nqo','nrm':'nrm','nso':'nso','nv':'nv','ny':'ny','oc':'oc','olo':'olo','om':'om','or':'or','os':'os','pa':'pa','pag':'pag','pam':'pam','pap':'pap','pcd':'pcd','pcm':'pcm','pdc':'pdc','pfl':'pfl','pi':'pi','pih':'pih','pl':'pl','pms':'pms','pnb':'pnb','pnt':'pnt','ps':'ps','pt':'pt','pwn':'pwn','qu':'qu','rm':'rm','rmy':'rmy','rn':'rn','ro':'ro','roa-rup':'roa-rup','roa-tara':'roa-tara','ru':'ru','rue':'rue','rup':'roa-rup','rw':'rw','sa':'sa','sah':'sah','sat':'sat','sc':'sc','scn':'scn','sco':'sco','sd':'sd','se':'se','sg':'sg','sgs':'bat-smg','sh':'sh','shi':'shi','shn':'shn','shy':'shy','si':'si','simple':'simple','sk':'sk','skr':'skr','sl':'sl','sm':'sm','smn':'smn','sn':'sn','so':'so','sq':'sq','sr':'sr','srn':'srn','ss':'ss','st':'st','stq':'stq','su':'su','sv':'sv','sw':'sw','szl':'szl','szy':'szy','ta':'ta','tay':'tay','tcy':'tcy','te':'te','tet':'tet','tg':'tg','th':'th','ti':'ti','tk':'tk','tl':'tl','tly':'tly','tn':'tn','to':'to','tpi':'tpi','tr':'tr','trv':'trv','ts':'ts','tt':'tt','tum':'tum','tw':'tw','ty':'ty','tyv':'tyv','udm':'udm','ug':'ug','uk':'uk','ur':'ur','uz':'uz','ve':'ve','vec':'vec','vep':'vep','vi':'vi','vls':'vls','vo':'vo','vro':'fiu-vro','wa':'wa','war':'war','wo':'wo','wuu':'wuu','xal':'xal','xh':'xh','xmf':'xmf','yi':'yi','yo':'yo','yue':'zh-yue','za':'za','zea':'zea','zgh':'zgh','zh':'zh','zh-classical':'zh-classical','zh-min-nan':'zh-min-nan','zh-yue':'zh-yue','zu':'zu','cz':'cs','dk':'da','epo':'eo','jp':'ja','zh-cn':'zh','zh-tw':'zh','cmn':'zh','egl':'eml','en-simple':'simple','nb':'no','w':'en','wikt':0,'q':0,'b':0,'n':0,'s':0,'chapter':0,'v':0,'voy':0};
// The above was generated using the following Python code:
// import requests
// import re
// data = requests.get("https://en.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=interwikimap&formatversion=2&format=json").json()['query']['interwikimap']
// prefix_data = {item["prefix"]: (re.findall("https://([^.]+).wikipedia.org/wiki/\$1$", item["url"])[0] if re.findall("https://([^.]+).wikipedia.org/wiki/\$1$", item["url"]) else 0) for item in data}
// print(f"const WP_PREFIXES = " + str(prefix_data).replace(" ", "") +";")
const WP_LANGUAGES = {"en": "English", "ceb": "Cebuano", "de": "German", "fr": "French", "sv": "Swedish", "nl": "Dutch", "ru": "Russian", "es": "Spanish", "it": "Italian", "pl": "Polish", "arz": "Egyptian Arabic", "zh": "Chinese", "ja": "Japanese", "uk": "Ukrainian", "vi": "Vietnamese", "war": "Waray", "ar": "Arabic", "pt": "Portuguese", "fa": "Persian", "ca": "Catalan", "id": "Indonesian", "sr": "Serbian", "ko": "Korean", "no": "Norwegian", "tr": "Turkish", "ce": "Chechen", "fi": "Finnish", "cs": "Czech", "hu": "Hungarian", "tt": "Tatar", "ro": "Romanian", "sh": "Serbo-Croatian", "eu": "Basque", "zh-min-nan": "Minnan", "ms": "Malay", "he": "Hebrew", "eo": "Esperanto", "hy": "Armenian", "da": "Danish", "bg": "Bulgarian", "uz": "Uzbek", "cy": "Welsh", "simple": "Simple English", "sk": "Slovak", "et": "Estonian", "be": "Belarusian", "azb": "South Azerbaijani", "el": "Greek", "kk": "Kazakh", "min": "Minangkabau", "hr": "Croatian", "lt": "Lithuanian", "gl": "Galician", "ur": "Urdu", "az": "Azerbaijani", "sl": "Slovenian", "lld": "Ladin", "ka": "Georgian", "nn": "Norwegian Nynorsk", "ta": "Tamil", "th": "Thai", "hi": "Hindi", "bn": "Bangla", "mk": "Macedonian", "zh-yue": "Cantonese", "la": "Latin", "ast": "Asturian", "lv": "Latvian", "af": "Afrikaans", "tg": "Tajik", "my": "Burmese", "sq": "Albanian", "te": "Telugu", "mg": "Malagasy", "mr": "Marathi", "bs": "Bosnian", "oc": "Occitan", "be-tarask": "Belarusian (Taraškievica orthography)", "br": "Breton", "ml": "Malayalam", "nds": "Low German", "sw": "Swahili", "ku": "Kurdish", "ky": "Kyrgyz", "lmo": "Lombard", "jv": "Javanese", "pnb": "Western Punjabi", "new": "Newari", "ht": "Haitian Creole", "vec": "Venetian", "pms": "Piedmontese", "ckb": "Central Kurdish", "ba": "Bashkir", "lb": "Luxembourgish", "su": "Sundanese", "ga": "Irish", "is": "Icelandic", "szl": "Silesian", "cv": "Chuvash", "pa": "Punjabi", "fy": "Western Frisian", "io": "Ido", "ha": "Hausa", "tl": "Tagalog", "an": "Aragonese", "wuu": "Wu", "diq": "Zazaki", "vo": "Volapük", "mzn": "Mazanderani", "sco": "Scots", "ig": "Igbo", "yo": "Yoruba", "kn": "Kannada", "ne": "Nepali", "als": "Alemannic", "gu": "Gujarati", "ia": "Interlingua", "avk": "Kotava", "crh": "Crimean Tatar", "bar": "Bavarian", "scn": "Sicilian", "bpy": "Bishnupriya", "ban": "Balinese", "qu": "Quechua", "mn": "Mongolian", "nv": "Navajo", "xmf": "Mingrelian", "si": "Sinhala", "ps": "Pashto", "frr": "Northern Frisian", "os": "Ossetic", "tum": "Tumbuka", "or": "Odia", "sd": "Sindhi", "sah": "Yakut", "bcl": "Central Bikol", "bat-smg": "Samogitian", "cdo": "Mindong", "gd": "Scottish Gaelic", "bug": "Buginese", "yi": "Yiddish", "ilo": "Iloko", "am": "Amharic", "li": "Limburgish", "nap": "Neapolitan", "gor": "Gorontalo", "mai": "Maithili", "fo": "Faroese", "hsb": "Upper Sorbian", "map-bms": "Basa Banyumasan", "shn": "Shan", "as": "Assamese", "zh-classical": "Literary Chinese", "eml": "Emiliano-Romagnolo", "ace": "Achinese", "ie": "Interlingue", "wa": "Walloon", "sa": "Sanskrit", "hyw": "Western Armenian", "sn": "Shona", "sat": "Santali", "zu": "Zulu", "mhr": "Eastern Mari", "lij": "Ligurian", "hif": "Fiji Hindi", "bjn": "Banjar", "km": "Khmer", "mrj": "Western Mari", "mni": "Manipuri", "hak": "Hakka Chinese", "ary": "Moroccan Arabic", "dag": "Dagbani", "pam": "Pampanga", "roa-tara": "Tarantino", "rue": "Rusyn", "bh": "Bhojpuri", "nso": "Northern Sotho", "co": "Corsican", "so": "Somali", "glk": "Gilaki", "vls": "West Flemish", "nds-nl": "Low Saxon", "mi": "Māori", "se": "Northern Sami", "myv": "Erzya", "tly": "Talysh", "kaa": "Kara-Kalpak", "sc": "Sardinian", "rw": "Kinyarwanda", "bo": "Tibetan", "kw": "Cornish", "vep": "Veps", "tk": "Turkmen", "kab": "Kabyle", "gv": "Manx", "gan": "Gan", "fiu-vro": "Võro", "mt": "Maltese", "zea": "Zeelandic", "mdf": "Moksha", "ab": "Abkhazian", "skr": "Saraiki", "ug": "Uyghur", "smn": "Inari Sami", "frp": "Arpitan", "gn": "Guarani", "pcd": "Picard", "udm": "Udmurt", "kv": "Komi", "csb": "Kashubian", "ks": "Kashmiri", "ay": "Aymara", "nrm": "Norman", "ang": "Old English", "lez": "Lezghian", "olo": "Livvi-Karelian", "nah": "Nāhuatl", "lfn": "Lingua Franca Nova", "mwl": "Mirandese", "fur": "Friulian", "pap": "Papiamento", "stq": "Saterland Frisian", "tw": "Twi", "lo": "Lao", "ln": "Lingala", "rm": "Romansh", "lad": "Ladino", "ext": "Extremaduran", "gom": "Goan Konkani", "dty": "Doteli", "tyv": "Tuvinian", "av": "Avaric", "koi": "Komi-Permyak", "dsb": "Lower Sorbian", "cbk-zam": "Chavacano", "dv": "Divehi", "lg": "Ganda", "ksh": "Colognian", "za": "Zhuang", "bxr": "Russia Buriat", "blk": "Pa'O", "gag": "Gagauz", "pfl": "Palatine German", "ff": "Fula", "szy": "Sakizaya", "haw": "Hawaiian", "pag": "Pangasinan", "tay": "Tayal", "bew": "Betawi", "pi": "Pali", "awa": "Awadhi", "zgh": "Standard Moroccan Tamazight", "tcy": "Tulu", "inh": "Ingush", "krc": "Karachay-Balkar", "xh": "Xhosa", "atj": "Atikamekw", "fon": "Fon", "to": "Tongan", "pdc": "Pennsylvania German", "mnw": "Mon", "arc": "Aramaic", "shi": "Tachelhit", "om": "Oromo", "ki": "Kikuyu", "tn": "Tswana", "xal": "Kalmyk", "gpe": "Ghanaian Pidgin", "nia": "Nias", "jam": "Jamaican Creole English", "kbp": "Kabiye", "wo": "Wolof", "nov": "Novial", "anp": "Angika", "kbd": "Kabardian", "dga": "Dagaare", "nqo": "N’Ko", "bi": "Bislama", "roa-rup": "Aromanian", "tpi": "Tok Pisin", "tet": "Tetum", "guw": "Gun", "jbo": "Lojban", "kg": "Kongo", "fj": "Fijian", "lbe": "Lak", "mad": "Madurese", "cu": "Church Slavic", "ty": "Tahitian", "pcm": "Nigerian Pidgin", "trv": "Taroko", "sm": "Samoan", "ami": "Amis", "srn": "Sranan Tongo", "kcg": "Tyap", "alt": "Southern Altai", "ltg": "Latgalian", "chr": "Cherokee", "gcr": "Guianan Creole", "btm": "Batak Mandailing", "ny": "Nyanja", "st": "Southern Sotho", "kus": "Kʋsaal", "ss": "Swati", "ee": "Ewe", "pih": "Norfuk / Pitkern", "gur": "Frafra", "got": "Gothic", "bm": "Bambara", "rmy": "Vlax Romani", "bbc": "Batak Toba", "ve": "Venda", "ts": "Tsonga", "dtp": "Central Dusun", "chy": "Cheyenne", "rn": "Rundi", "fat": "Fanti", "ik": "Inupiaq", "igl": "Igala", "guc": "Wayuu", "ady": "Adyghe", "ch": "Chamorro", "pnt": "Pontic", "iu": "Inuktitut", "pwn": "Paiwan", "sg": "Sango", "din": "Dinka", "ti": "Tigrinya", "dz": "Dzongkha", "kl": "Kalaallisut", "cr": "Cree"};
// https://meta.wikimedia.org/wiki/List_of_Wikipedias#All_Wikipedias_ordered_by_number_of_articles
mw.util.addCSS(`
.page-preview ol {
margin: 0 0.5em 0 1.5em;
padding: 0;
}
.page-preview dl {
margin-bottom: 0;
}
.page-preview p {
margin: 0;
}
.popup-fade-in-up {
animation: popup-fade-in-up ${animationSpeed}s ease forwards;
}
.popup-fade-in-down {
animation: popup-fade-in-down ${animationSpeed}s ease forwards;
}
.popup-fade-out-up {
animation: popup-fade-out-up ${animationSpeed}s ease forwards;
}
.popup-fade-out-down {
animation: popup-fade-out-down ${animationSpeed}s ease forwards;
}
@keyframes popup-fade-in-up {
0% {
opacity: 0;
transform: translate(0, 20px);
}
}
@keyframes popup-fade-in-down {
0% {
opacity: 0;
transform: translate(0, -20px);
}
}
@keyframes popup-fade-out-up {
100% {
opacity: 0;
transform: translate(0, -20px);
}
}
@keyframes popup-fade-out-down {
100% {
opacity: 0;
transform: translate(0, 20px);
}
}
.ring-loader {
margin: auto;
width: 24px;
height: 24px;
border-radius: 50%;
border-top: 5px solid black;
border-bottom: 5px solid black;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
animation: spin 1.2s linear infinite;
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
.preview-headerlink, .preview-headerlink:visited {
color: inherit;
font-weight: bold;
}
/* Hack: get the speaker icon on Wikipedia articles without having to load Phonos */
.ext-phonos .oo-ui-buttonElement-button:after {
content: "🔊";
}
.ext-phonos * {
display: inline !important;
padding: 0 !important;
margin: 0 !important;
}
`);
const definitionIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><g><path d="M15 2a7.65 7.65 0 00-5 2 7.65 7.65 0 00-5-2H1v15h4a7.65 7.65 0 015 2 7.65 7.65 0 015-2h4V2zm2.5 13.5H14a4.38 4.38 0 00-3 1V5s1-1.5 4-1.5h2.5z"></path><path d="M9 3.5h2v1H9z"></path></g></svg>`;
const glossaryIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><g><path d="M12.43 14.34A5 5 0 0110 15a5 5 0 113.95-2L17 16.09V3a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 001.45-.63z"></path><circle cx="10" cy="10" r="3"></circle></g></svg>`;
const articleIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><g><path d="M5 1a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V3a2 2 0 00-2-2zm0 3h5v1H5zm0 2h5v1H5zm0 2h5v1H5zm10 7H5v-1h10zm0-2H5v-1h10zm0-2H5v-1h10zm0-2h-4V4h4z"></path></g></svg>`;
const loader = document.createElement("div");
loader.className = "ring-loader";
const restAPI = new mw.Rest({ ajax: { headers: { "Api-User-Agent": "Gadget developed by [[User:Ioaxxere]]" } } });
let popupContainer = document.createElement("div");
let popup = document.createElement("div");
let popupContent = document.createElement("div");
popupContainer.style = `display: none; width: ${width}px; height: ${height}px; position: absolute; filter: drop-shadow(0px 30px 30px rgba(0, 0, 0, 0.15)) drop-shadow(0px 0px 1px #bcb8b6); z-index: 801`;
popupContainer.className = "page-preview";
popup.style = "box-sizing: border-box; height: 100%; background: #fff";
popupContent.style = "display: flex; flex-direction: column; border-radius: 2px; box-sizing: border-box; color: #202122; font-size: 14px; line-height: 20px; overflow: auto; height: 100%; overflow-wrap: break-word; scrollbar-width: thin";
document.body.append(popupContainer);
popupContainer.append(popup);
popup.append(popupContent);
let popupTimer;
let openLink;
let mouseX, mouseY;
function closePopup() {
if (!openLink) return;
openLink.dispatchEvent(new Event("pagePreviewClosed"));
openLink = null;
if (popup.classList.contains("popup-fade-in-up")) {
popup.classList.remove("popup-fade-in-up");
popup.classList.add("popup-fade-out-down");
} else {
popup.classList.remove("popup-fade-in-down");
popup.classList.add("popup-fade-out-up");
}
// Wait for animation to finish. If this line is removed, the popup will be transparent but still absorb clicks.
setTimeout(() => popupContainer.style.display = "none", animationSpeed * 1000);
}
popup.addEventListener("pointerenter", () => {
clearTimeout(popupTimer);
});
popup.addEventListener("pointerleave", () => {
clearTimeout(popupTimer);
popupTimer = setTimeout(closePopup, 300);
});
function processLink(link) {
let linkTitle = decodeURIComponent(link.href.split("/wiki/").pop().split(/[#?]/)[0]);
let titleNorm = linkTitle.toLowerCase();
let linkAnchor = decodeURIComponent(link.href.split("#")[1] || "");
let isWPlink = link.href.match(/^https:\/\/[^.]+.wikipedia.org\/wiki\//);
let isPreviewLink = link.closest(".page-preview");
// Start with various checks to determine whether a link should be processed.
if (link.matches(`nav a, .new, .preview-headerlink, .external, [href$="#"]`))
return;
if ((linkTitle === "Appendix:Glossary" && !linkAnchor) || (linkTitle !== "Appendix:Glossary" && !isWPlink && /^[a-z]/.test(linkAnchor)))
return;
if (link.href.startsWith("https://en.wiktionary.org/wiki/")) {
let titleObject = new mw.Title(linkTitle);
if (titleObject.namespace === 0) { // Mainspace
if (titleObject.title.endsWith("/translations"))
return;
} else if (titleObject.namespace === 100) { // Appendix space
let appendixLanguages = ["Adûni", "Afrihili", "Belter_Creole", "Black_Speech", "Bolak", "Communicationssprache", "Dothraki", "Eloi", "Gestures", "Glosa", "Goa'uld", "High_Valyrian", "Interlingue", "Interslavic", "Klingon", "Kotava", "Láadan", "Lapine", "Lingua_Franca_Nova", "Lojban", "Mandalorian", "Medefaidrin", "Minecraft", "Mundolinco", "Na'vi", "Neo", "Novial", "Noxilo", "Protologisms/Long_words", "Quenya", "Romanova", "Sindarin", "Snowclones", "Talossan", "Toki_Pona", "Unas"];
let appendixEntries = ["Boldface", "Capital_letter", "Glossary", "Italics", "Lowercase_letter", "Possessive", "Repetition", "Small_caps", "Strikethrough", "Subscript", "Superscript", "Underline", "Uppercase_letter"];
if (!appendixLanguages.some(lang => titleObject.title.startsWith(lang + "/")) && !appendixEntries.includes(titleObject.title))
return;
} else if (titleObject.namespace !== 118) { // Reconstruction space
return;
}
} else if (isWPlink) {
// Filter out invalid interwiki prefixes.
if (linkTitle.includes(":") && WP_PREFIXES[titleNorm.split(":")[0]] === 0)
return;
} else {
return;
}
link.addEventListener("pointerenter", (event) => {
clearTimeout(popupTimer);
// ignore links which are already open
if (link === openLink)
return;
if (!isPreviewLink)
closePopup();
// Track mouse movements.
mouseX = event.clientX;
mouseY = event.clientY;
// Fetch popup text immediately on hover to reduce delay.
let API_domain = link.href.split("/wiki/")[0];
let responsePromise;
if (isWPlink) {
let articleTitle = linkTitle;
if (linkTitle.includes(":") && WP_PREFIXES[titleNorm.split(":")[0]]) {
API_domain = "https://" + WP_PREFIXES[titleNorm.split(":")[0]] + ".wikipedia.org";
articleTitle = linkTitle.substring(linkTitle.indexOf(":") + 1);
}
responsePromise = fetch(API_domain + "/api/rest_v1/page/html/" + encodeURIComponent(articleTitle));
responsePromise = responsePromise.then(response => response.text());
} else {
responsePromise = restAPI.get("/v1/page/" + encodeURIComponent(linkTitle) + "/html");
}
popupTimer = setTimeout(() => {
popupContainer.style.display = "";
popupContent.innerHTML = "";
popupContent.append(loader);
popupContent.style.padding = "14px 16px 8px";
// Disable link hovering until animation has completed.
popupContent.style.pointerEvents = "none";
setTimeout(() => popupContent.style.pointerEvents = "", animationSpeed * 1000);
if (isPreviewLink) {
// Trigger fade-in-down animation.
popup.classList.remove("popup-fade-in-up");
popup.classList.remove("popup-fade-in-down");
popup.offsetLeft; // force reflow
popup.classList.add("popup-fade-in-down");
} else {
openLink = link;
openLink.dispatchEvent(new Event("pagePreviewOpened"));
// Get list of rects (lines) of the link, then find the one whose midpoint is closest to mouseY.
// This ensures that the code can correctly handle multi-line links.
let linkBlock = Array.from(link.getClientRects()).reduce((prev, next) => Math.abs((next.top + next.bottom) / 2 - mouseY) < Math.abs((prev.top + prev.bottom) / 2 - mouseY) ? next : prev);
// Horizontal position. Choose left or right depending on the side of the screen the mouse is in.
let leftPosition = mouseX - 30;
if (mouseX > document.documentElement.clientWidth / 2)
leftPosition = mouseX - width + 30;
// Ensure that the popup is at least 5px away from the side of the screen.
leftPosition = Math.max(5, Math.min(leftPosition, document.documentElement.clientWidth - width - 5));
popupContainer.style.left = leftPosition + document.documentElement.scrollLeft + "px";
// Vertical position. Prioritize fade-in-down unless there would be less than 10px of room above.
if (linkBlock.top > height + 10 || linkBlock.top > document.documentElement.clientHeight - linkBlock.bottom) {
popupContainer.style.top = linkBlock.top + document.documentElement.scrollTop - height + "px";
popup.classList.add("popup-fade-in-down");
popup.classList.remove("popup-fade-out-down");
popup.classList.remove("popup-fade-out-up");
popup.style.padding = "0 0 8px";
// Create a triangle on the bottom.
popup.style.clipPath = "polygon(0 0, 100% 0, 100% calc(100% - 8px), " + (mouseX - leftPosition + 8) + "px calc(100% - 8px), " + (mouseX - leftPosition) + "px 100%, " + (mouseX - leftPosition - 8) + "px calc(100% - 8px), 0 calc(100% - 8px))";
} else {
popupContainer.style.top = linkBlock.bottom + document.documentElement.scrollTop + "px";
popup.classList.add("popup-fade-in-up");
popup.classList.remove("popup-fade-out-down");
popup.classList.remove("popup-fade-out-up");
popup.style.padding = "8px 0 0";
// Create a triangle on top.
popup.style.clipPath = "polygon(0 8px, " + (mouseX - leftPosition - 8) + "px 8px, " + (mouseX - leftPosition) + "px 0, " + (mouseX - leftPosition + 8) + "px 8px, 100% 8px, 100% 100%, 0 100%)";
}
}
responsePromise.then(response => {
popupContent.innerHTML = "";
let responseDocument = new DOMParser().parseFromString(response, "text/html");
// Convert to absolute URLs.
responseDocument.querySelectorAll("a[href]").forEach(link => link.setAttribute("href", link.href));
let anchoredElement = responseDocument.getElementById(linkAnchor);
let popupHeader = document.createElement("div");
popupHeader.style = "font-size: 90%; color: #2f445c";
popupContent.append(popupHeader);
let iconContainer = document.createElement("span");
iconContainer.style = "float: right; margin-left: 10px; height: 20px";
popupHeader.append(iconContainer);
let titleLink = document.createElement("a");
titleLink.href = link.href;
titleLink.title = link.title;
titleLink.className = "preview-headerlink";
// Scrape entry content.
if (isWPlink) {
iconContainer.innerHTML = articleIcon;
titleLink.textContent = linkTitle.replaceAll("_", " ");
if (responseDocument.title)
titleLink.innerHTML = responseDocument.title; // sometimes gives HTML text
let WikipediaName = WP_LANGUAGES[API_domain.substr(8).split(".")[0]];
popupHeader.append(WikipediaName + " Wikipedia article for ", titleLink);
let articleContent = document.createElement("div");
articleContent.style.margin = "5px 0 0 10px";
let firstSection = responseDocument.querySelector("section");
if (firstSection && responseDocument.querySelector(`meta[property="mw:pageNamespace"][content="0"]`)) {
firstSection.querySelectorAll("b").forEach(boldElem => boldElem.outerHTML = boldElem.innerHTML);
firstSection.querySelectorAll(":scope > p, :scope > ul, :scope > ol").forEach(elem => articleContent.append(elem));
}
if (articleContent.childElementCount)
popupContent.append(articleContent);
} else if (linkTitle === "Appendix:Glossary") {
iconContainer.innerHTML = glossaryIcon;
titleLink.textContent = linkAnchor.replaceAll("_", " ");
popupHeader.append("Glossary definition of ", titleLink);
if (anchoredElement && anchoredElement.matches(".template-anchor")) {
let glossaryDefinition = anchoredElement.parentElement.nextElementSibling;
if (glossaryDefinition && glossaryDefinition.matches("dd")) {
glossaryDefinition.style = "margin: 5px 0 0 15px";
popupContent.append(glossaryDefinition);
}
}
} else {
let hasDefinitions = elem => Boolean(elem.querySelector("ol:not(.references), .ja-see, .zh-see"));
iconContainer.innerHTML = definitionIcon;
// Try to resolve the anchor element to #Chinese.
if (!anchoredElement && ["Cantonese", "Gan", "Hakka", "Hokkien", "Literary_Chinese", "Mandarin", "Middle_Chinese", "Old_Chinese", "Wu", "Xiang"].includes(linkAnchor))
anchoredElement = responseDocument.querySelector("#Chinese");
// Try to guess the anchor target in the following order: English, Chinese, Translingual, [first h2 on page]
// Always prioritize an L2 section which contains definitions.
if (!linkAnchor) {
let pageH2s = Array.from(responseDocument.querySelectorAll("h2"));
if (pageH2s.some(h2 => hasDefinitions(h2.parentElement)))
pageH2s = pageH2s.filter(h2 => hasDefinitions(h2.parentElement));
anchoredElement = pageH2s.find(h2 => h2.id === "English");
if (!anchoredElement) anchoredElement = pageH2s.find(h2 => h2.id === "Chinese");
if (!anchoredElement) anchoredElement = pageH2s.find(h2 => h2.id === "Translingual");
if (!anchoredElement) anchoredElement = pageH2s[0];
}
let displayTitle = document.createElement("strong");
displayTitle.textContent = linkTitle.split("/").pop().replaceAll("_", " ");
if (linkTitle.startsWith("Reconstruction:"))
displayTitle.textContent = "*" + displayTitle.textContent;
// Find localest section containing an h2.
let languageSection = anchoredElement;
while (languageSection && !languageSection.querySelector(":scope > h2"))
languageSection = languageSection.parentElement.closest("section");
// Make sure that the entry is well-formed.
if (languageSection) {
let language = languageSection.querySelector(":scope > h2").textContent;
let scrapeSection = anchoredElement.closest("section");
// Find localest section which contains any definitions.
while (scrapeSection && !hasDefinitions(scrapeSection))
scrapeSection = scrapeSection.parentElement.closest("section");
if (scrapeSection) {
let headwords = scrapeSection.querySelectorAll(".headword-line > strong");
if (new Set(Array.from(headwords).map(h => h.textContent)).size === 1) {
// If there is a single unique headword, replace the display title with that.
if (headwords[0].querySelector("a > img"))
displayTitle = headwords[0].querySelector("a > img").cloneNode();
else
displayTitle = headwords[0];
} else if (new Set(Array.from(headwords).map(h => h.cloneNode().outerHTML)).size === 1) {
// If there is a single unique set of element attributes, use that with the display title.
let temp = displayTitle.innerHTML;
displayTitle = headwords[0].cloneNode(); // this clears out the inner HTML
displayTitle.innerHTML = temp;
}
// Remove links in the headword (if there are any).
displayTitle.querySelectorAll("a").forEach(elem => elem.replaceWith(elem.textContent));
let ols = scrapeSection.querySelectorAll(":scope > ol:not(.references), section > ol:not(.references)");
if (anchoredElement.matches(".senseid")) {
Array.from(anchoredElement.parentElement.childNodes).forEach(child => {
if (child !== anchoredElement) {
child.remove();
}
});
ols = [anchoredElement.parentElement];
}
for (let ol of ols) {
let POS_container = document.createElement("div");
POS_container.style.margin = "8px 0 5px 0";
let POS = document.createElement("span");
POS.style = "font-size: 110%; font-weight: bold";
POS.textContent = ol.closest("section").firstChild.textContent;
POS_container.append(POS);
let tlb = ol.closest("section").querySelector(".headword-line ~ .usage-label-term");
if (tlb)
POS_container.append(document.createElement("br"), tlb);
popupContent.append(POS_container, ol);
}
// {{ja-see}} and {{zh-see}} don't follow the normal format...
for (let seeTemplate of scrapeSection.querySelectorAll(".ja-see, .zh-see"))
popupContent.append(seeTemplate);
}
titleLink.append(displayTitle);
popupHeader.append("Preview definitions of " + language + " ", titleLink);
} else {
titleLink.append(displayTitle);
popupHeader.append("Preview definitions of ", titleLink);
}
}
if (popupContent.childElementCount > 1) {
// Clean up HTML.
popupContent.querySelectorAll("link, .previewonly, .maintenance-line, .mw-empty-elt, .reference, .Inline-Template").forEach(elem => elem.remove());
if (!isWPlink)
popupContent.querySelectorAll("li > ul").forEach(elem => elem.remove()); // remove quotations
for (let elem of popupContent.querySelectorAll("*"))
elem.removeAttribute("id"); // avoid inadvertently repeating IDs within a page
} else if (isWPlink) {
// Display a message if no definitions were found in the section.
let noArticle = document.createElement("div");
noArticle.style = "margin: 5px 0 0 15px; font-size: 90%";
noArticle.textContent = "(article content could not be previewed)";
popupContent.append(noArticle);
} else if (linkAnchor && !anchoredElement) {
// Display a message if the anchor is invalid.
let noSectionFound = document.createElement("div");
noSectionFound.style = "margin: 10px 0 0 7.5px; font-size: 90%";
let strong = document.createElement("strong");
strong.textContent = linkAnchor.replaceAll("_", " ");
noSectionFound.append("The ", strong, " section was not found on this page.");
popupContent.append(noSectionFound);
} else {
// Display a message if no definitions were found in the section.
let noDefinitionsFound = document.createElement("div");
noDefinitionsFound.style = "margin: 5px 0 0 15px; font-size: 90%";
noDefinitionsFound.textContent = "(no definitions found)";
popupContent.append(noDefinitionsFound);
}
// Reduce the padding if a scrollbar is present. The intended padding is 16px on each side.
if (width - popupHeader.clientWidth > 32)
popupContent.style.paddingRight = Math.max(4, 48 + popupHeader.clientWidth - width) + "px";
});
// (Hack?) make sure that `link pointerleave` doesn't cause the popup to immediately close.
setTimeout(() => clearTimeout(popupTimer), 0);
}, isPreviewLink ? 1200 : 400);
});
link.addEventListener("pointerleave", () => {
clearTimeout(popupTimer);
if (link === openLink)
popupTimer = setTimeout(closePopup, 300);
});
link.addEventListener("pointermove", event => {
mouseX = event.clientX;
mouseY = event.clientY;
});
link.addEventListener("click", () => clearTimeout(popupTimer));
}
// Process all links.
document.querySelectorAll("a").forEach(processLink);
// Process links which are added to the DOM after the gadget has run.
let mo = new MutationObserver(events => events.forEach(event => event.addedNodes.forEach(node => {
if (node instanceof Element) {
if (node.tagName === "A")
processLink(node);
node.querySelectorAll("a").forEach(processLink);
}
})));
mo.observe(document.body, {childList: true, subtree: true});
}
if (mw.config.values.skin !== "minerva")
pagePreviews();
// </nowiki>