Wikipedia
testwiki
https://test.wikipedia.org/wiki/Main_Page
MediaWiki 1.46.0-wmf.21
first-letter
Media
Special
Talk
User
User talk
Wikipedia
Wikipedia talk
File
File talk
MediaWiki
MediaWiki talk
Template
Template talk
Help
Help talk
Category
Category talk
Thread
Thread talk
Summary
Summary talk
Test namespace 1
Test namespace 1 talk
Test namespace 2
Test namespace 2 talk
Draft
Draft talk
Campaign
Campaign talk
TimedText
TimedText talk
Module
Module talk
SecurePoll
SecurePoll talk
CNBanner
CNBanner talk
Translations
Translations talk
Event
Event talk
Topic
Newsletter
Newsletter talk
Wikipedia:Village pump
4
35019
735564
733843
2026-03-30T03:46:58Z
Pine
41485
/* Upcoming Wikimedia Café meetup regarding the the 2026-2027 Wikimedia Foundation Annual Plan */ new section
735564
wikitext
text/x-wiki
{{mbox|type=style|text=<p>Before posting here, remember that [[Wikipedia:What Test Wiki is not|Test Wikipedia is not a community]]. The idea of "community consensus" is therefore meaningless.<p>If you would like to start a discussion relating to this wiki, consider posting to the [[mail:wikitech-l|wikitech-l]] mailing list.}}
'''This is the test wiki for MediaWiki developers. It is not intended for research or other serious use of content. If you were trying to reach the encylopedic content, please use the [[:en:|English Wikipedia]].'''
'''Ce wiki est un wiki de test pour les développeurs MediaWiki. Il ne sert pas à la recherche ni à d'autres fins officielles de son contenu. Si vous recherchez le contenu encyclopédique, il se trouve sur la [[:fr:|Wikipedia française]].'''
'''미디어위키 개발자를 위한 테스트 위키입니다. 이는 내용의 연구나 기타 진지하게 사용하기 위한 것이 아닙니다. 백과사전적인 내용을 찾으려면, [[:ko:|한국어 위키백과를]] 이용하세요.'''
'''Este é a wiki de teste para desenvolvedores do MediaWiki. Não se destina a pesquisa ou outro uso sério de conteúdo. Se você estava tentando acessar o conteúdo da enciclopédia, use a [[:pt:|Wikipédia em português]].'''
'''Це тестова вікі для розробників MediaWiki. Цей сайт не призначений для якихось досліджень або наповнення контентом. Вікіпедія знаходиться [[:uk:|тут]].'''
'''Sta cuà ła xé ła Wiki de prova par i desviłupadori de MediaWiki. Sta Wiki no ła xé intendesta par ła riserca o altre doparasion da contegnui ençiclopèdeghi. Se te jeri drio intentar de catar fora contegnui da ençiclopedia, varda [[:vec:|Wikipèdia in Vèneto]].'''
'''这是一个供MediaWiki开发者测试的维基站点,请不要在此放置严肃的内容页面或进行研究。请访问[[:zh:|中文维基百科]]以访问百科全书内容。'''
There is an [[/Archive|archive]].
{{Wikipedia:Village pump/topic list}}
__TOC____NEWSECTIONLINK__<!--
####################################################
########## PLEASE LEAVE THE ABOVE LINES ##########
####################################################-->
==Script==
Hello, can someone please make edits that were blocked by an edit filter, see [https://test.wikipedia.org/w/index.php?title=Special:AbuseLog&wpSearchUser=LuniZunie], we are testing warning and reporting and do not want to report or revert the wrong user on normal Wikipedia and blow it up. Also are there autoconfirmed users? [[User:Pro-anti-air|Pro-anti-air]] ([[User talk:Pro-anti-air|talk]]) 03:50, 9 November 2025 (UTC)
:I don't know what you want me to do exactly, as I cannot edits this page, import to this title, or move a page to this title. I have made [[User:Koavf/scripts/WikiShield.js]] if that helps. Ping me if you want me to do something. —[[User:Koavf|Justin (<span style="color:grey">ko'''a'''vf</span>)]]<span style="color:red">❤[[User talk:Koavf|T]]☮[[Special:Contributions/Koavf|C]]☺[[Special:Emailuser/Koavf|M]]☯</span> 04:03, 9 November 2025 (UTC)
:It's a global filter, pinging @[[User:Codename Noreste|Codename Noreste]]. [[User:Tanbiruzzaman|Tanbiruzzaman]] ([[User talk:Tanbiruzzaman|talk]]) 04:05, 9 November 2025 (UTC)
:@[[User:Pro-anti-air|Pro-anti-air]], to bypass the filter you need to make 2+ edit in other page. [[User:Tanbiruzzaman|Tanbiruzzaman]] ([[User talk:Tanbiruzzaman|talk]]) 04:07, 9 November 2025 (UTC)
::I'm confused, what rights do you get from editing more? Isn't autoconfirmed just 4 days? [[User:Pro-anti-air|Pro-anti-air]] ([[User talk:Pro-anti-air|talk]]) 04:10, 9 November 2025 (UTC)
: [[User:Koavf|Koavf]], [[User:Pro-anti-air|Pro-anti-air]], and [[User:Tanbiruzzaman|Tanbiruzzaman]]: I adjusted the global filter so that such false positives do not happen again. [[User:Codename Noreste|Codename Noreste]] ([[User talk:Codename Noreste|talk]]) 04:21, 9 November 2025 (UTC)
::Thank you! [[User:Pro-anti-air|Pro-anti-air]] ([[User talk:Pro-anti-air|talk]]) 04:26, 9 November 2025 (UTC)
:: @[[User:Codename Noreste|Codename Noreste]] Thank you so much!! [[User:LuniZunie|LuniZunie]] ([[User talk:LuniZunie|talk]]) 16:47, 9 November 2025 (UTC)
== Report concerning [[Special:Contributions/Tanbiruzzammn|Tanbiruzzammn]] ==
* {{vandal|Tanbiruzzammn}}
Vandalism. Long-term abuse. impersonation of User:Tanbiruzzaman[[User:TenWhile6| ]]<small>[[:m:Special:MyLanguage/User:TenWhile6/XReport|XReport]]</small> --[[User:MathXplore|MathXplore]] ([[User talk:MathXplore|talk]]) 13:44, 9 December 2025 (UTC)
:Account is locked. I nuked the pages. -[[User:Barras|<span style="color:blue; font-family:Bookman Old Style">'''Barras'''</span>]] [[User Talk:Barras|<span style="color:red; font-family:Bookman Old Style">'''talk'''</span>]] 21:45, 9 December 2025 (UTC)
== Report concerning [[Special:Contributions/Bucheon|Bucheon]] ==
* {{vandal|Bucheon}}
Long-term abuse[[User:TenWhile6| ]]<small>[[:m:Special:MyLanguage/User:TenWhile6/XReport|XReport]]</small> --[[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 10:25, 19 February 2026 (UTC)
== Versions and dates ==
On the [[Special:Version]] page, I see:
{| class="wikitable"
!Product
!Version
|-
|[https://www.mediawiki.org/ MediaWiki]
|[[mw:MediaWiki_1.46/wmf.17|1.46.0-wmf.17]] [[git:mediawiki/core/+/8644e6d2bb489940eb0028286386746dd40a5711|(8644e6d)]]
00:34, 3 March 2026
|}
which suggests that version .17 was released today, March 3rd.
However, the [[mw:MediaWiki_1.46/Roadmap|Roadmap]] says that version .18 should be released today instead.
Are these values out of synch or am I reading this wrong? [[Special:Contributions/~2026-13668-13|~2026-13668-13]] ([[User talk:~2026-13668-13|talk]]) 03:38, 3 March 2026 (UTC)
:I now see version .18 listed in that table:
:{| class="wikitable"
!Product
!Version
|-
|[https://www.mediawiki.org/ MediaWiki]
|[[mw:MediaWiki_1.46/wmf.18|1.46.0-wmf.18]] [[git:mediawiki/core/+/9784b5fa1e290e0ff423b8c68c1d061e910f9075|(9784b5f)]]
02:08, 3 March 2026
|}
:(And I've worked out how to put tables in replies.) [[Special:Contributions/~2026-13668-13|~2026-13668-13]] ([[User talk:~2026-13668-13|talk]]) 06:17, 3 March 2026 (UTC)
== Upcoming Wikimedia Café meetup regarding the [[:meta:Wikimedia Foundation Annual Plan/2026-2027|the 2026-2027 Wikimedia Foundation Annual Plan]] ==
{{tmbox
| image = [[File:Wikimedia Café logo in plain SVG format.svg|45px]]
| type=notice
| text = Hello! There will be a '''[[:meta:Wikimedia Café|Wikimedia Café]]''' meetup on '''Saturday, 11 April 2026 at 14:00 UTC''' ([https://zonestamp.toolforge.org/1775916000 timestamp conversion tool]), focusing on the [[:meta:Wikimedia Foundation Annual Plan/2026-2027|the 2026-2027 Wikimedia Foundation Annual Plan]]. The featured guests will be {{Noping|KStineRowe (WMF)|label1=Kelsi Stine-Rowe}} (senior manager, [[:meta:Movement Communications|Movement Communications]], Wikimedia Foundation), and {{Noping|Samwalton9 (WMF)|label1=Sam Walton}} (senior product manager, [[:mw:Moderator Tools|Moderator Tools]], Wikimedia Foundation). <br />
In addition to this Café session, [[:meta:Wikimedia Foundation Annual Plan/2026-2027/Collaboration|several additional meetings regarding the Annual Plan are listed on the Collaboration page]], and you may participate on the [[:meta:Talk:Wikimedia Foundation Annual Plan/2026-2027|talk page]]. <br />
This Café meetup will be approximately two hours long. Attendees may choose to attend only for a part. Please see the Café page for more information, including [[:meta:Wikimedia Café#Signups for the April 2026 session|how to register]]. <br />
[[File:Buntstifte Eberhard Faber crop 64h.jpg|860px|alt=cropped image of colored pencils]]
}}
<span style="white-space:nowrap;">[[User:Pine|<span style="color:#01796f; text-shadow:#00BFFF 0 0 1.0em">↠Pine</span>]] [[User talk:Pine|<span style="color:DeepSkyBlue">(<b style="color:#FFDF00;text-shadow:#FFDF00 0 0 1.0em">✉</b>)</span>]]</span> 03:46, 30 March 2026 (UTC)
5hgt6s65l7nv431p1mwgxlf36qmcdoi
Test 10
0
97176
735563
724136
2026-03-30T00:04:54Z
InternetArchiveBot
34092
Rescuing 0 sources and tagging 1 as dead.) #IABot (v2.0.9.5
735563
wikitext
text/x-wiki
{| class="wikitable sortable
! Rank
! Country
! Year
! Titles
! Notes
! References
|-
| 1
| {{flag | United States}}
| 2013
| 275.232
| New titles and re-editions
| <ref name="publishingtechnology.com"/>
|-
| 2
| {{flag | China}}
| 2013
| 208.418
| New titles and re-editions
| <ref name="publishingtechnology.com">{{cite web
| url=http://www.publishingtechnology.com/2014/10/ipa-report-says-global-publishing-productivity-is-up-but-growth-is-down/| title=IPA report says global publishing productivity is up, but growth is down }}</ref>
|-
| 3
| {{flag | United Kingdom}}
| 2020
|
| 186.000
| <ref name="World Intellectual Property Organization-2022">{{Cite book
| url=https://www.wipo.int/edocs/pubdocs/en/wipo-pub-1064-2022-en-the-global-publishing-industry-in-2020.pdf
| title=The Global Publishing Industry in 2020
| publisher=[[World Intellectual Property Organization]]
| year=2022
| isbn=978-92-805-3395-8
| language=en}}</ref>
|-
| 4
| {{flag | Japan}}
| 2017
| 139.078
| New titles and re-editions
| <ref>{{cite web
| author=Japan Book Publishers Association
| url=http://www.jbpa.or.jp/en/pdf/pdf01.pdf
| title=An Introduction to Publishing in Japan 2017-2018
| publisher=Istat - National institute of statistic
| date=2017
| access-date=March 25, 2018
| archive-date=February 3, 2019}}</ref>
|-
| 5
| {{flag | Indonesia}}
| 2020
| 135.081
|
| <ref>{{cite web
| url=https://isbn.perpusnas.go.id/Home/Statistik#isbnPertahun
| title=Statistik - ISBN Perpustakaan Nasional Republik Indonesia
}}{{Dead link|date=March 2026 |bot=InternetArchiveBot |fix-attempted=yes }}</ref>
|-
| 6
| {{flag | Italy}}
| 2020
| 125.948
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 7
| {{flag | Russia}}
| 2019
| 115.171
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 8
| {{flag | France}}
| 2018
| 106.799
|
| <ref name="wipo2018">{{cite web
| title=The Global Publishing Industry in 2018
| url=https://www.wipo.int/edocs/pubdocs/en/wipo_pub_1064_2019.pdf
| archive-url=https://web.archive.org/web/20210215220517/https://www.wipo.int/edocs/pubdocs/en/wipo_pub_1064_2019.pdf
| archive-date=2021-02-15
| publisher=World Intellectual Property Organization}}</ref>
|-
| 9
| {{flag | Iran}}
| 2018
| 102.691
| New and revised
| <ref>{{cite web
| url=https://www.hamshahrionline.ir/news/439435/%D8%A7%D9%88%D8%B6%D8%A7%D8%B9-%D9%88-%D8%A7%D8%AD%D9%88%D8%A7%D9%84-%D9%86%D8%B4%D8%B1-%D8%AF%D8%B1-%D8%B3%D8%A7%D9%84-%DB%B9%DB%B7-%D8%A8%D9%87-%D8%B1%D9%88%D8%A7%DB%8C%D8%AA-%D8%A2%D9%85%D8%A7%D8%B1}} >
| title=اوضاع و احوال نشر در سال ۹۷ به روایت آمار
| language=Persian
| date=May 16, 2018
| website=Hamshahrionline
| access-date=August 13, 2022}}</ref>
|-
| 10
| {{flag | India}}
| 2013
| 90.000
| total: revised editions not included; 26% in [[Standard Hindi|Hindi]], 24% in [[English language in India|English]], and the rest in other [[Languages of South Asia|Indian languages]]
| <ref>{{cite web|url=http://www.buchmesse.de/images/fbm/dokumente-ua-pdfs/2013/book_market_india_2013.pdf_40366.pdf|title=Book Market in India|date=November 30, 2013|access-date=March 9, 2015|archive-url=https://web.archive.org/web/20150425075716/http://www.buchmesse.de/images/fbm/dokumente-ua-pdfs/2013/book_market_india_2013.pdf_40366.pdf|archive-date=2015-04-25|url-status=dead}}</ref><ref>{{Cite web |url=http://www.internationalpublishers.org/images/annual-reports/ipa_ar_online.pdf |title=Annual Report |date=October 2014 |publisher=International Publishers Association |access-date=August 13, 2022 |archive-url=https://web.archive.org/web/20160815154214/http://www.internationalpublishers.org/images/annual-reports/ipa_ar_online.pdf |archive-date=2016-08-15}}</ref>
|-
| 11
| {{flag | Turkey}}
| 2020
| 88.975
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 12
| {{flag | Spain}}
| 2020
| 83.622
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 13
| {{flag | Germany}}
| 2018
| 79.916
|
| <ref name=wipo2018/>
|-
| 14
| {{flag | South Korea}}
| 2018
| 63.476
| New
| <ref>{{cite web
| url=http://www.kpa21.or.kr/publishing-info/statistics/
| title=Korean publishers association official statistics
| publisher=Kpa21.or.kr
| access-date=2019-03-17}} (In Korean)</ref>
|-
| 15
| {{flag | Brazil}}
| 2018
| 46.829
|
| <ref name=wipo2018/>
|-
| 16
| {{flag | Taiwan}}
| 2010
| 28.084
| 43,258 total
| <ref>Government Information Office, Republic of China (Taiwan) {{cite web
| url=http://www.moc.gov.tw/ccaImages/download/0/22151185653.doc
| title=Summary
| access-date=2013-06-13
| archive-url=https://web.archive.org/web/20140107163255/http://www.moc.gov.tw/ccaImages/download/0/22151185653.doc
| archive-date=January 7, 2014 }}</ref>
|-
| 17
| {{flag | Vietnam}}
| 2009
| 24.589
|
| <ref name="ASEAN">{{cite web
| url=http://mabopa.com.my/upload/attc/ABPA%20AGM%202010%20REPORT%20(Print).pdf
| title=ASEAN book publishers association report 2010
| access-date=2012-06-14
| archive-date=2018-12-25
| archive-url=https://web.archive.org/web/20181225193015/http://mabopa.com.my/upload/attc/ABPA%20AGM%202010%20REPORT%20(Print).pdf%20
| url-status=dead
}}</ref>
|-
| 18
| {{flag | Ukraine}}
| 2019
| 24.416
|
| <ref>[http://ukrbook.net/statistika/statistika_2019.htm Ukrainian Book Chamber]</ref>
|-
| 19
| {{flag | Egypt}}
| 2019
| 23.000
|
| <ref>{{Cite web|last=السند|first=محمد عبد|date=2021-06-27|title=دراسة : مصر الأكثر نشاطا بمجال نشر الكتب عربيا قبل جائحة كورونا|url=https://almalnews.com/%d8%af%d8%b1%d8%a7%d8%b3%d8%a9-%d9%85%d8%b5%d8%b1-%d8%a7%d9%84%d8%a3%d9%83%d8%ab%d8%b1-%d9%86%d8%b4%d8%a7%d8%b7%d8%a7-%d8%a8%d9%85%d8%ac%d8%a7%d9%84-%d9%86%d8%b4%d8%b1-%d8%a7%d9%84%d9%83%d8%aa%d8%a8/|access-date=2022-02-15|website=جريدة المال|language=ar}}</ref>
|-
| 20
| {{flag | Poland}}
| 2015
| 21.130
| 34,920 total
| <ref>[http://www.bookinstitute.pl/upload/Files/polish_book_market_2016_www.pdf Polish Book Institute] {{Webarchive
| url=https://web.archive.org/web/20170211235238/http://www.bookinstitute.pl/upload/Files/polish_book_market_2016_www.pdf
| date=2017-02-11 }}.</ref>
|-
| 21
| {{flag | Australia}}
| 2020
| 19.241
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 22
| {{flag | Portugal}}
| 2020
| 18.925
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 23
| {{flag | Mexico}}
| 2020
| 18.713
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 24
| {{flag | Colombia}}
| 2017
| 17.066
| 18,508 total
| <ref>{{cite web
| url=https://camlibro.com.co/estadisticas-sector-editorial-en-colombia-2017
| title=Cámara Colombiana del Libro -statistics 2017
| date=29 October 2018
| format=XLXS
| access-date=17 June 2019
| archive-date=26 September 2019
| archive-url=https://web.archive.org/web/20190926171703/https://camlibro.com.co/estadisticas-sector-editorial-en-colombia-2017
}}</ref>
|-
| 25
| {{flag | Netherlands}}
| 2014
| 16.502
| Only physical Dutch new titles
| <ref>[https://web.archive.org/web/20120508022705/http://www.kvb.nl/feiten-en-cijfers/kerncijfers Koninklijke Vereniging van het Boekenvak]</ref>
|-
| 26
| {{flag | Czechia}}
| 2020
| 16.474
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 27
| {{flag | Malaysia}}
| 2015
| 15.354
| Declined since 2013 at 19,987
| <ref>{{cite web
| url=http://mabopa.com.my/index.php/article/549/
| title=Malaysian Book Publishers Association - Country Report: Malaysia 2016
| date=7 May 2016
| format=PDF
| access-date=2012-06-14
| archive-date=2016-10-20
| archive-url=https://web.archive.org/web/20161020021610/http://mabopa.com.my/index.php/article/549/
| url-status=dead
}}</ref>
|-
| 28
| {{flag | Romania}}
| 2008
| 14.984
|
| <ref>{{cite web
| author=Autori: Mirabela Tiron, Izabela Badarau
| url=http://www.zf.ro/companii/la-fiecare-miliard-de-euro-din-pib-in-romania-se-publica-113-titluri-noi-de-carti-la-fel-ca-in-anglia-5151343/
| title=La fiecare miliard de euro din PIB, in Romania se publica 113 titluri noi de carti, la fel ca in Anglia
| work=Ziarul Financiar
| date=2009-12-04
| access-date=2012-06-14}}</ref>
|-
| 29
| {{flag | Serbia}}
| 2020
| 14.901
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 30
| {{flag | Hong Kong}}
| 2005
| 14.603
|
| <ref>{{cite web|url=http://www.abigraf.org.br/index.php/downloads/doc_download/557-hong-kong-books-a-periodicals |title=Hong Kong: Books & Periodicals |publisher=Abigraf.org.br |date=2006-07-24 |access-date=2012-06-14 |url-status=dead |archive-url=https://web.archive.org/web/20120330193456/http://www.abigraf.org.br/index.php/downloads/doc_download/557-hong-kong-books-a-periodicals |archive-date=2012-03-30 }}</ref>
|-
| 31
| {{flag | Norway}}
| 2020
| 14.114
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 32
| {{flag | Belgium}}
| 1991
| 13.913
|
| <ref name=unesco>[http://www.ine.es/prensa/np542.pdf INE]</ref>
|-
| 33
| {{flag | Bulgaria}}
| 2019
| 12.194
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 34
| {{flag | Switzerland}}
| 2001
| 12.156
|
| <ref name="Centro">
| {{cite web
| url=http://www.cerlalc.org/secciones/libro_desarrollo/Centro_America.pdf
| title=Producción Y Comercio Internacional Del Libro En Centro América
| access-date=2012-06-14
| archive-url=https://web.archive.org/web/20130912151508/http://www.cerlalc.org/secciones/libro_desarrollo/Centro_America.pdf
| archive-date=2013-09-12
| url-status=dead }}</ref>
|-
| 35
| {{flag | Singapore}}
| 2007
| 12.000
|
| <ref>{{cite web
| url=http://singaporebookpublishers.sg/newsletters/SBPACountryReport.pdf
| title=SBPA - Publishing in Singapore
| access-date=2012-06-14
| url-status=dead
| archive-url=https://web.archive.org/web/20140107162630/http://singaporebookpublishers.sg/newsletters/SBPACountryReport.pdf
| archive-date=2014-01-07 }}</ref>
|-
| 36
| {{flag | Argentina}}
| 2019
| 11.514
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 37
| {{flag | Denmark}}
| 2020
| 10.715
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 38
| {{flag | Canada}}
| 2020
| 10.433
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 39
| {{flag | Finland}}
| 2020
| 10.208
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 40
| {{flag | Hungary}}
| 2019
| 9.589
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 41
| {{flag | Greece}}
| 2020
| 9.583
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 42
| {{flag | Slovakia}}
| 2006
| 9.400
|
| <ref>{{cite web
| url=http://spravy.pravda.sk/zaujimavosti/clanok/245773-pocet-vydanych-knih-od-90-rokov-stupol/
| title=Počet vydaných kníh od 90. rokov stúpol
| date=July 29, 2007
| website=Pravda
| language=Slovak
| access-date=August 13, 2022
| archive-url=https://web.archive.org/web/20140107165619/https://spravy.pravda.sk/zaujimavosti/clanok/245773-pocet-vydanych-knih-od-90-rokov-stupol/
| archive-date=January 7, 2014}}</ref>
|-
| 43
| {{flag | Austria}}
| 2020
| 8.711
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 44
| {{flag | Israel}}
| 2013
| 8.411
|
| <ref>[http://web.nli.org.il/sites/NLI/English/library/depositing/statistics/Pages/lgd-statistics-2013.aspx The National Library of Israel.]</ref>
|-
| 45
| {{flag | Belarus}}
| 2020
| 8.205
|
| <ref>[https://be.natbook.org.by/novosti/knigavydanne-belarusi-za-2020-god National Book Chamber of Belarus] {{Webarchive|url=https://web.archive.org/web/20210815070351/https://be.natbook.org.by/novosti/knigavydanne-belarusi-za-2020-god |date=2021-08-15 }} (In Belarusian)</ref>
|-
| 46
| {{flag | Sweden}}
| 2021
| 8.118
| 30,857 total
| <ref>{{cite web
| url=https://forlaggare.se/wp-content/uploads/2022/02/Fo%CC%88rlagsstatistik_2021.pdf
| title=Förlagsstatistik 2021
| publisher=Svenska Förlaggare Föreningen
| access-date=2022-06-04
| page=20 }}</ref>
|-
| 47
| {{flag | Chile}}
| 2020
| 7.058
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 48
| {{flag | Peru}}
| 2020
| 6.885
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 49
| {{flag | Thailand}}
| 2018
| 6.750
| Only trade sector data; educational sector data not available
| <ref name=wipo2018/>
|-
| 50
| {{flag | Estonia}}
| 2020
| 5.809
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 51
| {{flag | South Africa}}
| 1995
| 5.418
|
| <ref name=unesco3>UNESCO [http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_Africa.html "Africa"] {{webarchive
| url=https://web.archive.org/web/20060618094722/http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_Africa.html
| date=June 18, 2006 }}, Book production: number of titles by UDC classes, UNESCO Institute of Statistics</ref>
|-
| 52
| {{flag | Slovenia}}
| 2020
| 5.076
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 53
| {{flag | Moldova}}
| 2020
| 4.559
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 54
| {{flag | Ecuador}}
| 2020
| 4.153
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 55
| {{flag | Sri Lanka}}
| 1996
| 4.115
|
| <ref name=unesco2>{{cite web
| url=http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_Asia.html
| title="Asia", Book production: number of titles by UDC classes
| publisher=UNESCO Institute of Statistics
| archive-url=https://web.archive.org/web/20090205190318/http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_Asia.html
| archive-date=2009-02-05
| url-status=dead}}</ref>
|-
| 56
| {{flag | Algeria}}
| 2008
| 3.955
|
| <ref>{{cite web |url=http://www.umc.edu.dz/images/2016_productions-_et_receptions_culturelles_musique_cinema_abdelkader_abdelillah-03.pdf |title=L'industrie du livre et l'offre de lecture en Algerie |last=Abdellilah |first=Abdelkader |language=French |access-date=August 13, 2022 |archive-url=https://web.archive.org/web/20220302015906/http://www.umc.edu.dz/images/2016_productions-_et_receptions_culturelles_musique_cinema_abdelkader_abdelillah-03.pdf |archive-date=March 2, 2022}}</ref>
|-
| 57
| {{flag | Pakistan}}
| 2012
| 3.811
| Total; 2,943 in [[Urdu]] and 868 in English
| <ref>[http://www.nlp.gov.pk/upload/English/PNB_English_2012.pdf THE PAKISTAN NATIONAL BIBLIOGRAPHY 2012] {{webarchive
| url=https://web.archive.org/web/20131105142726/http://www.nlp.gov.pk/upload/English/PNB_English_2012.pdf
| date=November 5, 2013 }}</ref>
|-
| 58
| {{flag | Morocco}}
| 2018/2019
| 3.677
|
| <ref>{{Cite report
| url=http://www.fondation.org.ma/web/article/191
| title=Edition et Livre au Maroc 2018 / 2019
| date=2020
| publisher=King Abdul-Aziz Al Saoud Foundation for Islamic Studies and Human Sciences
| location=Casablanca
| issn=2605-6380}}</ref>
|-
| 59
| {{flag | Myanmar}}
| 1993
| 3.660
|
| <ref name="unesco2" />
|-
| 60
| {{flag | Lithuania}}
| 2019
| 3.479
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 61
| {{flag | Uruguay}}
| 2018
| 3.231
|
| <ref name=wipo2018/>
|-
| 62
| {{flag | Afghanistan}}
| 1990
| 2.795
|
| <ref name="unesco2" />
|-
| 63
| {{flag | New Zealand}}
| 2020
| 2.526
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 64
| {{flag | Saudi Arabia}}
| 2014
| 2.387
|
| <ref>{{cite web
| url=https://www.internationalpublishers.org/market-insights/country-reports/410-ipa-country-report-saudi-arabia
| title=International Publishers Association country report: Saudi Arabia
| date=June 30, 2016
| access-date=March 25, 2018
| archive-url=https://web.archive.org/web/20190126220829/https://www.internationalpublishers.org/market-insights/country-reports/410-ipa-country-report-saudi-arabia
| archive-date=January 26, 2019
| url-status=dead}}</ref>
|-
| 65
| {{flag | Latvia}}
| 2020
| 2.375
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 66
| {{flag | Venezuela}}
| 2018
| 2.275
|
| <ref name=wipo2018/>
|-
| 67
| {{flag | Costa Rica}}
| 2018
| 2.158
|
| <ref name=wipo2018/>
|-
| 68
| {{flag | Lebanon}}
| 2020
| 2.000
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 69
| {{flag | Luxembourg}}
| 2001
| 2.000
|
| <ref name="Centro" />
|-
| 70
| {{flag | Dominican Republic}}
| 2018
| 1.866
|
| <ref name=wipo2018/>
|-
| 71
| {{flag | Ireland}}
| 2020
| 1.773
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 72
| {{flag | Iceland}}
| 2019
| 1.767
|
| <ref>{{cite book
| publisher=National and University Library of Iceland
| title=Icelandic National Bibliography
| chapter-url= https://utgafuskra.is/statistics.jsp?lang=1
| chapter=Statistical summary of material published in Iceland in the year 2019
| access-date=2021-03-02}}</ref>
|-
| 73
| {{flag | Bolivia}}
| 2018
| 1.578
|
| <ref name=wipo2018/>
|-
| 74
| {{flag | Philippines}}
| 1996
| 1.507
|
| <ref name="unesco2" />
|-
| 75
| {{flag | Kyrgyzstan}}
| 2018
| 1.455
|
| <ref name=wipo2018/>
|-
| 76
| {{flag | Nigeria}}
| 1991
| 1.314
|
| <ref name="unesco3" />
|-
| 77
| {{flag | Kazakhstan}}
| 1996
| 1.226
|
| <ref name="unesco2" />
|-
| 78
| {{flag | Syria}}
| 2004
| 1.138
|
| <ref>{{cite web
| title=Information on the Syrian book market
| url=http://www.book-fair.com/en/networking/fairs_markets/book_markets/syria/
| author=Frankfurt Book Fair
| access-date=12 April 2011
| url-status=dead
| archive-url=https://web.archive.org/web/20101214080556/http://book-fair.com/en/networking/fairs_markets/book_markets/syria/
| archive-date=14 December 2010 }}</ref>
|-
| 79
| {{flag | Guatemala}}
| 2018
| 1.042
|
| <ref name=wipo2018/>
|-
| 80
| {{flag | Paraguay}}
| 2018
| 1.007
|
| <ref name=wipo2018/>
|-
| 81
| {{flag | Uzbekistan}}
| 1996
| 1.003
|
| <ref name="unesco2" />
|-
| 82
| {{flag | Eritrea}}
| 2015
| 970
|
| <ref name="unesco3" />
|-
| 83
| {{flag | Panama}}
| 2018
| 940
|
| <ref name=wipo2018/>
|-
| 84
| {{flag | Cyprus}}
| 1996
| 930
|
| <ref name="unesco2" />
|-
| 85
| {{flag | Cuba}}
| 2020
| 728
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 86
| {{flag | Tunisia}}
| 1996
| 720
|
| <ref name=unesco3/>
|-
| 87
| {{flag | El Salvador}}
| 2018
| 661
|
| <ref name=wipo2018/>
|-
| 88
| {{flag | Georgia}}
| 1996
| 581
|
| <ref name=unesco2/>
|-
| 89
| {{flag | Azerbaijan}}
| 1996
| 542
|
| <ref name=unesco2/>
|-
| 90
| {{flag | Jordan}}
| 1996
| 511
|
| <ref name=unesco2/>
|-
| 91
| {{flag | Malta}}
| 2020
| 511
|
| <ref name="World Intellectual Property Organization-2022" />
|-
| 92
| {{flag | Turkmenistan}}
| 1994
| 450
|
| <ref name=unesco2/>
|-
| 93
| {{flag | Fiji}}
| 1994
| 401
|
| <ref name=unesco4>UNESCO [http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_Oceania.html "Oceania"] {{webarchive
| url=https://web.archive.org/web/20060618095332/http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_Oceania.html
| date=June 18, 2006 }}, Book production: number of titles by UDC classes, UNESCO Institute of Statistics</ref>
|-
| 94
| {{flag | Armenia}}
| 1996
| 396
|
| <ref name=unesco2/>
|-
| 95
| {{flag | Albania}}
| 1991
| 390
|
| <ref name=unesco/>
|-
| 96
| {{flag | Kenya}}
| 1994
| 300
|
| <ref name=unesco3/>
|-
| 97
| {{flag | United Arab Emirates}}
| 1993
| 293
|
| <ref name=unesco2/>
|-
| 98
| {{flag | Uganda}}
| 1996
| 288
|
| <ref name=unesco3/>
|-
| 99
| {{flag | Mongolia}}
| 1992
| 285
|
| <ref name=unesco2/>
|-
| 100
| {{flag | Ethiopia}}
| 1991
| 240
|
| <ref name=unesco3/>
|-
| 101
| {{flag | Zimbabwe}}
| 1992
| 232
|
| <ref name=unesco3/>
|-
| 102
| {{flag | Vatican City}}
| 1996
| 228
|
| <ref name=unesco/>
|-
| 103
| {{flag | Qatar}}
| 1996
| 209
|
| <ref name=unesco2/>
|-
| 104
| {{flag | Kuwait}}
| 1992
| 196
|
| <ref name=unesco2/>
|-
| 105
| {{flag | Tanzania}}
| 1990
| 172
|
| <ref name=unesco3/>
|-
| 106
| {{flag | Botswana}}
| 1991
| 158
|
| <ref name=unesco3/>
|-
| 107
| {{flag | Tajikistan}}
| 1996
| 132
|
| <ref name=unesco2/>
|-
| 108
| {{flag | Papua New Guinea}}
| 1991
| 122
|
| <ref name=unesco4/>
|-
| 109
| {{flag | Madagascar}}
| 1996
| 119
|
| <ref name=unesco3/>
|-
| 110
| {{flag | Malawi}}
| 1996
| 117
|
| <ref name=unesco3/>
|-
| 111
| {{flag | Palestine}}
| 1996
| 114
|
| <ref name=unesco2/>
|-
| 112
| {{flag | Namibia}}
| 1990
| 106
|
| <ref name=unesco3/>
|-
| 113
| {{flag | Honduras}}
| 2018
| 102
|
| <ref name=wipo2018/>
|-
| 114
| {{flag | Brunei}}
| 2009
| 91
|
| <ref name="ASEAN"/>
|-
| 115
| {{flag | Laos}}
| 1995
| 88
|
| <ref name=unesco2/>
|-
| 116
| {{flag | Benin}}
| 1994
| 84
|
| <ref name=unesco3/>
|-
| 117
| {{flag | Mauritius}}
| 1996
| 80
|
| <ref name=unesco3/>
|-
| 118
| {{flag | Réunion}}
| 1992
| 69
|
| <ref name=unesco3/>
|-
| 119
| {{flag | Democratic Republic of the Congo}}
| 1992
| 64
|
| <ref name=unesco3/>
|-
| 120
| {{flag | Andorra}}
| 1994
| 57
|
| <ref name=unesco/>
|-
| 121
| {{flag | Suriname}}
| 1996
| 47
|
| <ref name=unesco1>{{cite web
| url=http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_America.html
| title=Book production: number of titles by UDC classes
| publisher=UNESCO Institute of Statistics
| archive-url=https://web.archive.org/web/20040328192933/http://www.uis.unesco.org/TEMPLATE/html/CultAndCom/Table_IV_5_America.html
| archive-date=2004-03-28}}</ref>
|-
| 122
| {{flag | American Samoa}}
| 1980
| 46
|
| <ref>Whitney, G. (1989). The UNESCO book production statistics. Book Research Quarterly, 5(4), 12–29.</ref>
|-
| 123
| {{flag | Guyana}}
| 1996
| 42
|
| <ref name=unesco1/>
|-
| 124
| {{flag | Monaco}}
| 1990
| 41
|
| <ref name=unesco/>
|-
| 125
| {{flag | Bahrain}}
| 1996
| 40
|
| <ref name=unesco2/>
|-
| 126
| {{flag | Ghana}}
| 1992
| 28
|
| <ref name=unesco3/>
|-
| 127
| {{flag | Nicaragua}}
| 2018
| 27
|
| <ref name=wipo2018/>
|-
| 128
| {{flag | Libya}}
| 1994
| 26
|
| <ref name=unesco3/>
|-
| 129
| {{flag | Angola}}
| 1995
| 22
|
| <ref name=unesco3/>
|-
| 130
| {{flag | Gambia}}
| 1996
| 14
|
| <ref name=unesco3/>
|-
| 131
| {{flag | Mali}}
| 1995
| 14
|
| <ref name=unesco3/>
|-
| 132
| {{flag | Burkina Faso}}
| 1996
| 12
|
| <ref name=unesco3/>
|-
| 133
| {{flag | Oman}}
| 1996
| 7
|
| <ref name=unesco2/>
|}
<references />
[[Category:Malaysia]]
1nfmsn2kez7xyg7ireermxrc0pikd6h
444
0
103125
735579
735272
2026-03-30T10:18:15Z
DWalden (WMF)
44719
735579
wikitext
text/x-wiki
this is an edit
this is an edit
== fsdaf ==
{{slink|Main Page|What we do on this wiki}} [[User:Newslinger|Newslinger]] ([[User talk:Newslinger|talk]]) 10:59, 12 April 2019 (UTC)
== 123 ==
{{slink|777|fsdaf}} [[User:Newslinger|Newslinger]] ([[User talk:Newslinger|talk]]) 02:02, 18 March 2021 (UTC)
tjtrvb3r3ipy31vnsprngu927d2hyr7
735580
735579
2026-03-30T10:18:59Z
~2026-18850-85
73288
735580
wikitext
text/x-wiki
this is an edit
== fsdaf ==
{{slink|Main Page|What we do on this wiki}} [[User:Newslinger|Newslinger]] ([[User talk:Newslinger|talk]]) 10:59, 12 April 2019 (UTC)
== 123 ==
{{slink|777|fsdaf}} [[User:Newslinger|Newslinger]] ([[User talk:Newslinger|talk]]) 02:02, 18 March 2021 (UTC)
55havt28ib1e1dz3tni7vgrpewa2rpr
735581
735580
2026-03-30T10:19:15Z
~2026-18850-85
73288
735581
wikitext
text/x-wiki
this is an edit
this is an edit
== fsdaf ==
{{slink|Main Page|What we do on this wiki}} [[User:Newslinger|Newslinger]] ([[User talk:Newslinger|talk]]) 10:59, 12 April 2019 (UTC)
== 123 ==
{{slink|777|fsdaf}} [[User:Newslinger|Newslinger]] ([[User talk:Newslinger|talk]]) 02:02, 18 March 2021 (UTC)
tjtrvb3r3ipy31vnsprngu927d2hyr7
User:Nardog/sandbox.js
2
115814
735549
734851
2026-03-29T15:32:09Z
Nardog
40946
735549
javascript
text/javascript
/* globals ve */
// window.dn?.disable?.();
window.diffnowExtraSelector = '.catchangesviewer-table td:nth-child(2) > .mw-changeslist-links > span:first-child > a, .listtools-last > a, .mw-special-AbuseLog form li > a[href^="/wiki/Special:AbuseLog/"], .unseendiff';
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
mw.loader.using(['mediawiki.util', 'user.options'], function diffNow() {
let isHist = mw.config.get('wgAction') === 'history';
let selector = `:where(.mw-changeslist-diff, .mw-changeslist-diff-cur, .mw-changeslist-groupdiff, .mw-fr-reviewlink > a, .mw-fr-hist-difflink a, .mw-history-histlinks a, .mw-logevent-loglines a[href*="&diff="], .mw-fr-pending-changes-table .cdx-docs-link, .mw-special-AbuseLog #mw-content-text > form > ul > li > a:last-of-type[href*="&diff="], .mw-deletedcontribs-tools > a:first-child, .mw-undelete-revlist > li > a:first-of-type[href*="&diff="], #merge li > a:first-of-type[href*="&diff="], .mw-history-compareselectedversions-button, .consecudiff > a${window.diffnowExtraSelector ? ', ' + window.diffnowExtraSelector : ''}):not(.external${window.diffnowNegativeSelector ? ', ' + window.diffnowNegativeSelector : ''})`;
let switchSelector = '.diffnow-differences-prevlink, .diffnow-differences-nextlink, .diffnow-switch';
let diffs = [], count = 0, expanded = new WeakSet();
window.dn = diffs;
let sanitize = href => {
let url = new URL(href, location.href);
let newParams = new URLSearchParams(), hasId;
['diff', 'direction', 'oldid', 'target', 'timestamp'].forEach(k => {
if (url.searchParams.has(k)) {
let v = url.searchParams.get(k);
newParams.set(k, v);
hasId = hasId || v > 0;
}
});
if (!hasId && url.searchParams.has('title')) {
return `${url.search.match(/[&?](title=[^&]*)/)[1]}&${newParams}`;
}
return String(newParams);
};
let getDiff = comp => {
if (typeof comp === 'string') {
let sanitized = sanitize(comp);
return diffs.find(diff => (
diff.queries.has(comp) || diff.queries.has(sanitized)
));
}
return diffs.find(diff => diff.$diff.is(comp));
};
let containers = [];
window.dn._c = containers;
class Diff {
constructor(href, $link) {
this.aborter = new AbortController();
this.queries = new Set();
this.setLink(href, $link);
diffs.push(this);
}
setLink(href, $link) {
if (this.href) {
if ($link.is(this.$link)) {
if (this.aborter) {
this.aborter.abort();
} else if (this.isVisible()) {
this.close();
} else {
this.$anchor = this.getAnchor();
this.append();
}
return;
}
if ($link.is(this.$outerLink) && this.isVisible()) {
this.close();
return;
}
if (this.getAnchor($link).is(this.$anchor)) {
this.markLink(false);
} else {
this.close();
}
}
this.href = href;
this.queries.add(href);
let sanitized = sanitize(href);
if (sanitized) {
this.queries.add(sanitized);
}
this.$link = $link;
this.isSwitch = $link.is(switchSelector);
if (!this.isSwitch) {
this.$outerLink = $link;
}
this.$anchor = this.getAnchor();
if (this.aborter) {
this.markLink();
} else {
this.append();
}
}
append(response) {
if (response) {
delete this.aborter;
this.findDiff(response);
this.setQueries();
this.polishDiff();
}
let $container = this.getContainer(true);
if (!$container[0].isConnected) {
if (this.$anchor.is('tr')) {
let cols = this.$anchor.children().get()
.reduce((acc, cell) => acc + cell.colSpan, 0);
$('<tr>').addClass('diffnow-row').append(
$('<td>').attr('colspan', cols).append($container)
).insertAfter(this.$anchor);
} else if (this.$anchor.is('div')) {
this.$anchor.after($container);
} else {
this.$anchor.append($container);
}
}
this.attached = true;
filterContexts(this.$diff);
window.addEventListener('resize', onResize);
if (!this.$link.hasClass('diffnow-link-loaded')) {
mw.requestIdleCallback(() => {
this.markLinks();
this.markSeen();
});
}
if ($container[0].getBoundingClientRect().top < 0) {
$container[0].scrollIntoView();
}
this.markLink(true);
if (response) {
mw.hook('wikipage.content').fire($container);
}
}
findDiff(response) {
if (typeof response !== 'string') throw '';
let $diff = $($.parseHTML(response))
.filter('.diff[data-mw-interface]');
if (!$diff.length) {
$diff = $diff.end().find('.diff[data-mw-interface]');
if (!$diff.length) throw '';
}
this.$diff = $($diff[0]);
}
setQueries() {
let newPerma = this.$diff[0].querySelector(
'#mw-diff-ntitle1 > strong > a, #differences-nextlink'
);
if (newPerma) {
this.newId = parseInt(mw.util.getParamValue('oldid', newPerma.search));
this.newTitle = newPerma.title;
}
let oldPerma = this.$diff[0].querySelector(
'#mw-diff-otitle1 > strong > a, #differences-prevlink'
);
if (oldPerma) {
this.oldId = parseInt(mw.util.getParamValue('oldid', oldPerma.search));
this.oldTitle = oldPerma.title;
}
let isSamePage = !oldPerma || this.newTitle === this.oldTitle;
this.isSingle = isSamePage && !this.$diff[0].querySelector('.diff-multi');
if (this.newId) {
if (this.newId === this.oldId) {
throw 'nonfatal';
}
if (this.oldId) {
this.queries.add(`diff=${this.newId}&oldid=${this.oldId}`);
}
this.isLast = !this.$diff[0].querySelector('#differences-nextlink');
if (this.isSingle) {
this.queries.add(`diff=prev&oldid=${this.newId}`);
if (this.isLast && this.oldId) {
this.queries.add(`diff=0&oldid=${this.oldId}`);
this.queries.add(`diff=cur&oldid=${this.oldId}`);
}
}
}
if (this.oldId && this.isSingle) {
this.queries.add(`diff=next&oldid=${this.oldId}`);
}
this.queries.forEach(q => {
let other = getDiff(q);
if (other && other !== this) {
other.setLink(this.href, this.$link);
throw 'nonfatal';
}
});
if (this.oldId) {
diffs.forEach(diff => {
if (diff.isLast && diff.newId === this.oldId) {
diff.markNotLast(this);
}
});
}
}
async polishDiff() {
mw.hook('wikipage.diff').fire(this.$diff);
let prevLink = this.$diff[0].querySelector('#differences-prevlink');
let nextLink = this.$diff[0].querySelector('#differences-nextlink');
$([prevLink, nextLink].filter(Boolean)).attr('href', (_, href) => (
href.replace('&diffonly=1', '').replace('&expand-url=1', '')
));
[['previousdiff', prevLink], ['nextdiff', nextLink]].forEach(([key, link]) => {
if (link && !mw.messages.exists(key)) {
mw.messages.set(key, link.textContent);
}
});
if (!nextLink && this.isLast) {
this.$diff.find('#mw-diff-ntitle4').empty().append(
$('<a>').attr({
class: 'diffnow-checknext diffnow-switch',
href: mw.util.getUrl(this.newTitle, {
diff: 'next',
oldid: this.newId
}),
title: this.newTitle
}).text('Check for newer edit')
);
}
this.$diff.find('.mw-diff-movedpara-left, .mw-diff-movedpara-right')
.attr('href', (_, href) => `#diffnow-${count}-${href.slice(1)}`);
this.$diff.find('a[name^="movedpara"]')
.attr('name', (_, name) => `diffnow-${count}-${name}`);
count++;
this.$diff.find('[id]').addClass(function () {
return 'diffnow-' + this.id;
}).removeAttr('id');
if (this.$diff[0].querySelector('.mw-thanks-thank-link')) {
mw.loader.load('ext.thanks.corethank');
mw.config.set('thanks-confirmation-required', true);
}
if (!this.isSingle) {
let keys = ['nextdiff', 'previousdiff']
.filter(s => !mw.messages.exists(s));
if (keys.length) {
await mw.loader.using('mediawiki.api');
await new mw.Api().loadMessagesIfMissing(keys);
}
this.$diff.find('.diff-multi').append(
$('<div>').append(
$('<div>').append(
$('<a>').attr({
class: 'diffnow-multinext diffnow-switch',
href: mw.util.getUrl(this.oldTitle, {
diff: 'next',
oldid: this.oldId
}),
title: this.oldTitle
}).text(mw.msg('nextdiff'))
),
$('<div>').append(
$('<a>').attr({
class: 'diffnow-multiprev diffnow-switch',
href: mw.util.getUrl(this.newTitle, {
diff: 'prev',
oldid: this.newId
}),
title: this.newTitle
}).text(mw.msg('previousdiff'))
)
)
);
}
}
getAnchor($link) {
let isSwitch;
if ($link) {
isSwitch = $link.is(switchSelector);
} else {
$link = this.$link;
isSwitch = this.isSwitch;
}
if (isSwitch) {
return getDiff($link.closest('.diff')).$anchor;
}
return $link.closest('li, tr, .mw-history-compareselectedversions');
}
getContainer(create) {
let $container = this.$anchor.is('tr')
? this.$anchor.next('.diffnow-row').find('> td > .diffnow')
: this.$anchor.is('div')
? this.$anchor.next('.diffnow')
: this.$anchor.children('.diffnow');
if (create) {
if ($container.length) {
this.attachDiff($container);
} else {
$container = this.createContainter();
}
}
return $container;
}
attachDiff($container) {
let $oldDiff = $container.children('.diff');
if (!$oldDiff.is(this.$diff)) {
if ($oldDiff.length) {
getDiff($oldDiff).detachDiff();
}
$container.children('.diffnow-tools-top').after(this.$diff);
}
$container.find('.diffnow-difflink').attr('href', this.href);
}
createContainter() {
let $container = containers.pop();
if ($container) {
this.attachDiff($container);
return $container;
}
return $('<div>').addClass('diffnow').append(
$('<div>').addClass('diffnow-tools diffnow-tools-top').append(
$('<button>').attr({
class: 'diffnow-button diffnow-close oo-ui-icon-collapse',
title: 'Hide this diff'
})
),
this.$diff,
$('<div>').addClass('diffnow-tools diffnow-tools-bottom').append(
$('<button>').attr({
class: 'diffnow-button diffnow-scrollup oo-ui-icon-collapse',
title: 'Scroll to top'
}),
$('<button>').attr({
class: 'diffnow-button diffnow-scrolldown oo-ui-icon-expand',
title: 'Scroll to bottom'
}),
$('<button>').attr({
class: 'diffnow-button diffnow-close oo-ui-icon-close',
title: 'Hide this diff'
}),
$('<a>').attr({
class: 'diffnow-button diffnow-difflink oo-ui-icon-newWindow oo-ui-image-progressive',
href: this.href,
target: '_blank',
title: 'Open diff page'
})
)
)
.on('click', '.diffnow-close', this.collapse)
.on('click', '.diffnow-scrollup', this.scrollUp)
.on('click', '.diffnow-scrolldown', this.scrollDown)
.on(
'click keydown',
'.diffnow-context-collapsed > td, .diffnow-context-expanded > .diff-marker',
this.toggleContext
);
}
isVisible() {
return !!this.$diff?.[0].offsetParent;
}
markLink(visible = this.isVisible()) {
if (this.isSwitch) {
this.getContainer().toggleClass('diffnow-switching', !!this.aborter);
if (!this.$outerLink) return;
}
this.$outerLink
.toggleClass('diffnow-link-loading', !!this.aborter)
.toggleClass('diffnow-link-loaded', !this.aborter)
.toggleClass('diffnow-link-open', !this.isSwitch && visible);
}
markLinks() {
$(selector).filter((_, link) => {
let href = link.getAttribute('href');
return this.queries.has(href) || this.queries.has(sanitize(href));
}).addClass('diffnow-link-loaded');
if (!isHist) return;
$('.mw-history-compareselectedversions-button').toggleClass(
'diffnow-link-loaded',
!!getDiff(getHistHref())
);
}
markSeen() {
if (this.$link.is('.mw-rcfilters-ui-highlights-enhanced-nested:nth-child(n+2) .mw-changeslist-diff')) {
this.$anchor.nextAll().addBack()
.removeClass('mw-changeslist-watchedunseen mw-changeslist-line-watched mw-enhanced-watched')
.addClass('mw-changeslist-watchedseen mw-changeslist-line-not-watched');
return;
}
let $li = this.$anchor.closest('li, table');
let $unseen = $li.filter('.mw-changeslist-watchedunseen')
.add($li.find('.mw-changeslist-watchedunseen'));
if (!$unseen.length) return;
$unseen.removeClass('mw-changeslist-watchedunseen')
.addClass('mw-changeslist-watchedseen');
$li.filter('.mw-changeslist-line-watched')
.add($li.find('.mw-changeslist-line-watched'))
.removeClass('mw-changeslist-line-watched mw-enhanced-watched')
.addClass('mw-changeslist-line-not-watched');
}
async markNotLast(newDiff) {
this.isLast = false;
this.queries = new Set([...this.queries].filter(q => (
!/^title=|(?:^|[&?])(?:diff|oldid)=(?:0|cur)(?:&|$)/.test(q)
)));
this.$diff.find(
'.diffnow-mw-diff-ntitle1 a, .diffnow-mw-diff-ntitle1 .history-deleted'
).first().text(
newDiff.$diff.find(
'#mw-diff-otitle1 a, #mw-diff-otitle1 .history-deleted'
).first().text()
);
this.$diff.find('.diff-ntitle .mw-diff-edit a')
.attr('href', (_, href) => href + '&oldid=' + this.newId);
if (!mw.messages.exists('nextdiff')) {
await mw.loader.using('mediawiki.api');
await new mw.Api().loadMessagesIfMissing(['nextdiff']);
}
this.$diff.find('.diffnow-checknext')
.attr('class', 'diffnow-differences-nextlink')
.text(mw.msg('nextdiff'));
}
detachDiff() {
this.$diff?.detach();
this.markLink(false);
}
close(adjustFocus) {
this.detachDiff();
let $container = this.getContainer();
if ($container.length) {
let $row = $container.closest('.diffnow-row');
containers.push($container.detach());
$row.remove();
}
if (!adjustFocus) return;
setTimeout(() => {
this.$anchor.find('a[href]').last().each(function () {
this.focus();
this.blur();
});
});
}
collapse(e) {
e.preventDefault();
let $container = $(e.delegateTarget);
let diff = getDiff($container.children('.diff'));
if (diff.$link[0].getBoundingClientRect().top < 0) {
diff.$anchor[0].scrollIntoView();
$container.fadeOut('fast', () => {
diff.close(true);
setTimeout(() => {
diff.$anchor[0].scrollIntoView();
$container.removeAttr('style');
});
});
} else {
diff.close(true);
}
}
scrollUp(e) {
e.preventDefault();
let y = e.delegateTarget.getBoundingClientRect().top;
let ch = document.documentElement.clientHeight, buffer = ch / 2;
y -= y + 5 > buffer ? ch : buffer;
window.scrollBy({ top: y, behavior: 'smooth' });
}
scrollDown(e) {
e.preventDefault();
let y = e.delegateTarget.getBoundingClientRect().bottom;
let buffer = document.documentElement.clientHeight / 2;
if (y - 5 > buffer) {
y -= buffer;
}
window.scrollBy({ top: y, behavior: 'smooth' });
}
toggleContext(e) {
if (e.type === 'keydown' && (
e.which !== 13 && e.which !== 32 ||
e.ctrlKey || e.shiftKey || e.metaKey || e.altKey
)) {
return;
}
e.preventDefault();
let $row = $(e.target.closest('tr'));
if ($row.hasClass('diffnow-context-expanded')) {
$row.removeClass('diffnow-context-expanded')
.addClass('diffnow-context-collapsed')
.children().attr({
tabindex: 0,
role: 'button',
title: 'Expand'
});
expanded.delete($row[0]);
} else {
$row.removeClass('diffnow-context-collapsed')
.addClass('diffnow-context-expanded')
.children('.diff-context').removeAttr('tabindex role title')
.siblings('.diff-marker').attr('title', 'Collapse');
expanded.add($row[0]);
}
}
destroy() {
diffs.splice(diffs.indexOf(this), 1);
this.$link.removeClass('diffnow-link-loading diffnow-link-loaded diffnow-link-open');
this.getContainer().removeClass('diffnow-switching');
}
}
let filterContexts = $diff => {
$diff.find('.diff-context.diff-side-deleted > div').each(function () {
let $row = $(this.closest('tr'))
.removeClass('diffnow-context-expanded');
if (this.scrollHeight > this.clientHeight) {
if (expanded.has($row[0])) {
$row.addClass('diffnow-context-expanded')
.children('.diff-marker').attr({
tabindex: 0,
role: 'button',
title: 'Collapse'
});
} else {
$row.addClass('diffnow-context-collapsed').children().attr({
tabindex: 0,
role: 'button',
title: 'Expand'
});
}
} else {
$row.removeClass('diffnow-context-collapsed')
.children().removeAttr('tabindex role title');
}
});
};
let onResize = mw.util.debounce(() => {
filterContexts($('.diffnow > .diff'));
}, 250);
let notif;
let showError = async (href, msg) => {
notif = await mw.notify([
document.createTextNode(msg || `Couldn't load the diff`),
$('<p>').append(
$('<a>').attr({
href: href,
target: '_blank'
}).text('Open it in a new tab')
)[0]
], { autoHideSeconds: 'long', tag: 'diffnow', type: 'error' });
};
let getHistHref = () => {
let formData = new FormData(document.getElementById('mw-history-compare'));
return mw.util.getUrl(null, {
diff: formData.get('diff'),
oldid: formData.get('oldid')
});
};
$(document.body).on('click.diffnow', 'a, .mw-history-compareselectedversions-button', async function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
!this.matches(selector) && !this.matches(switchSelector)
) {
return;
}
e.preventDefault();
let $link = $(this);
let isCompare = $link.hasClass('mw-history-compareselectedversions-button');
let href = isCompare ? getHistHref() : this.pathname + this.search;
let diff = getDiff(href);
try {
if (diff) {
diff.setLink(href, $link);
return;
}
if (!isCompare && this.origin !== location.origin) {
throw '';
}
diff = new Diff(href, $link);
let url = new URL(href, location.href);
url.searchParams.set('diffonly', 1);
url.searchParams.set('action', 'render');
let promise = fetch(url, { signal: diff.aborter.signal });
mw.loader.using(['mediawiki.diff', 'mediawiki.diff.styles']);
if (notif) {
notif.close();
notif = null;
}
diff.append(await (await promise).text());
} catch (error) {
if (diff && !diff.attached) {
diff.destroy();
}
if (error.name === 'AbortError') {
notif = await mw.notify('Diff loading canceled', { tag: 'diffnow' });
} else if (error !== 'nonfatal') {
showError(href, error);
console.error(error);
}
}
});
let css = mw.loader.addStyleTag(`${selector} {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand.svg);
background-position: right;
background-repeat: no-repeat;
background-size: 10px 10px;
padding-right: 12px;
}
.mw-history-compareselectedversions-button {
background-position: right 6px center;
background-size: 14px 14px;
padding-right: 24px !important;
}
.skin-timeless .mw-history-compareselectedversions-button {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand-invert.svg);
}
.skin-timeless .mw-history-compareselectedversions-button.diffnow-link-loaded {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/reload-invert.svg) !important;
}
.skin-timeless .mw-history-compareselectedversions-button.diffnow-link-open {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/collapse-invert.svg) !important;
}
.diffnow-link-loading {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cstyle type='text/css'%3Ecircle%7Banimation:bounce-delay 600ms infinite ease-in-out both;animation-delay:-80ms;transform-origin:center;transform-box:fill-box%7Dcircle:first-of-type%7Banimation-delay:-160ms%7Dcircle:last-of-type%7Banimation-delay:0ms%7D@keyframes bounce-delay%7B0%25,50%25,100%25%7Btransform:scale(0)%7D20%25%7Bopacity:0.87;transform:scale(1)%7D%7D%3C/style%3E%3Ccircle cx='2.5' cy='10' r='2.5'/%3E%3Ccircle cx='10' cy='10' r='2.5'/%3E%3Ccircle cx='17.5' cy='10' r='2.5'/%3E%3C/svg%3E") !important;
}
.diffnow-link-loaded {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/reload.svg) !important;
}
.diffnow-link-open, .diffnow-context-expanded > .diff-marker {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/collapse.svg) !important;
}
.diffnow {
background: var(--background-color-base, #fff);
border: var(--border-base, 1px solid #a2a9b1);
border-radius: 4px;
position: relative;
}
.mw-history-compareselectedversions + .diffnow {
margin-top: 0.3em;
}
.diffnow-tools {
display: flex;
position: sticky;
background-color: var(--background-color-backdrop-light, rgba(255,255,255,0.65));
z-index: 1;
}
.diffnow-tools-top {
top: 0;
border-radius: 4px;
}
.diffnow-tools-bottom {
bottom: 0;
}
.diffnow-tools > .diffnow-button {
cursor: pointer;
height: 24px;
padding: 0;
box-sizing: content-box;
background-position: center;
background-repeat: no-repeat;
background-size: 16px 16px;
background-color: var(--background-color-transparent, transparent);
flex-grow: 1;
}
.diffnow-tools > .diffnow-button:hover {
background-color: var(--background-color-button-quiet--hover, rgba(0,24,73,0.027));
}
.diffnow-tools > .diffnow-button:active {
background-color: var(--background-color-button-quiet--active, rgba(0,24,73,0.082));
}
.diffnow-tools-top > .diffnow-button {
border: none;
border-radius: 3px 3px 0 0;
}
.diffnow-tools-bottom > .diffnow-button {
border: var(--border-base, 1px solid #a2a9b1);
border-radius: 99px;
margin: 4px;
height: 20px;
}
.diffnow-switching > .diff {
transition: opacity 200ms;
opacity: 0.4;
}
.diffnow-checknext, .diffnow-checknext:visited {
color: var(--color-subtle, #54595d);
}
.diffnow :not(.diffnow-context-expanded) > .diff-context > div {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.diffnow-context-collapsed > td,
.diffnow-context-expanded > .diff-marker {
cursor: pointer !important;
background-position: center;
background-repeat: no-repeat;
background-size: 12px 12px;
}
.diffnow-context-collapsed > .diff-marker {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand.svg);
}
.diffnow-context-collapsed > .diff-context a {
pointer-events: none;
}
.diffnow .diff-multi > div {
display: flex;
justify-content: space-around;
}
.diffnow > .diff td div {
word-break: break-word;
}
.diffnow .mw-diff-inline-changed ins,
.diffnow .mw-diff-inline-changed del {
white-space: pre-wrap;
}
.diffnow-row .diffnow {
border: none;
}
.diffnow-row > td {
padding: 0 !important;
}
td.mw-changeslist-line-inner {
width: 100%;
}
.mw-enhanced-rc .diffnow > .diff td {
padding: 0.33em 0.5em;
}
.cdx-table__table-wrapper:has(.diffnow-row) {
overflow-x: visible;
}
span.mw-history-histlinks-current,
span.mw-history-histlinks-previous {
padding-right: 12px;
}
.client-js .diffnow .mw-anonuserlink {
padding-right: 0;
}`);
mw.loader.using(['oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-interactions']);
diffs._disable = () => {
$(document.body).off('.diffnow');
css.remove();
};
if (isHist) {
$(document.body).on('change.diffnow', '#mw-history-compare', function () {
let $buttons = $('.mw-history-compareselectedversions-button');
let diff = getDiff(getHistHref());
if (diff) {
$buttons.addClass('diffnow-link-loaded');
if ($buttons.is(diff.$link) && diff.isVisible()) {
diff.$link.addClass('diffnow-link-open');
} else {
$buttons.removeClass('diffnow-link-open');
}
} else {
$buttons.removeClass('diffnow-link-loaded diffnow-link-open');
}
});
}
let added, tempLoaded, ipInfoLoaded;
mw.hook('wikipage.content').add(async () => {
await new Promise(mw.requestIdleCallback);
if (!document.querySelector(selector)) return;
let modules = ['mediawiki.diff', 'mediawiki.diff.styles'];
let cspn = mw.config.get('wgCanonicalSpecialPageName');
let loadTemp = !tempLoaded &&
Number(mw.user.options.get('checkuser-temporary-account-enable')) &&
!['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(cspn);
if (loadTemp) {
modules.push('ext.checkUser.tempAccounts', 'ext.checkUser.styles');
}
if (Number(mw.user.options.get('checkuser-userinfocard-enable'))) {
modules.push('ext.checkUser.userInfoCard');
}
let loadIpInfo;
if (Number(mw.user.options.get('ipinfo-beta-feature-enable'))) {
modules.push('ext.ipInfo', 'ext.ipInfo.styles');
loadIpInfo = !ipInfoLoaded &&
['Contributions', 'DeletedContributions', 'IPContributions'].includes(cspn);
}
let promise = mw.loader.using(modules);
if (loadTemp || loadIpInfo) {
tempLoaded = loadTemp;
ipInfoLoaded = loadIpInfo;
await promise;
// https://gerrit.wikimedia.org/g/mediawiki/core/+/master/resources/src/startup/mediawiki.loader.js
let makeRequire = (moduleObj, basePath) => moduleName => {
let relParts = moduleName.match(/^((?:\.\.?\/)+)(.*)$/);
if (!relParts) {
return mw.loader.require(moduleName);
}
let baseDirParts = basePath.split('/');
baseDirParts.pop();
let prefixes = relParts[1].split('/');
prefixes.pop();
let prefix;
while ((prefix = prefixes.pop())) {
if (prefix === '..') {
baseDirParts.pop();
}
}
let fileName = baseDirParts.join('/');
if (fileName) {
fileName += '/';
}
fileName += relParts[2];
if (Object.hasOwn(moduleObj.packageExports, fileName)) {
return moduleObj.packageExports[fileName];
}
let scriptFiles = moduleObj.script.files;
if (!Object.hasOwn(scriptFiles, fileName)) {
throw Error('Cannot require undefined file ' + fileName);
}
let result;
let fileContent = scriptFiles[fileName];
if (typeof fileContent === 'function') {
let moduleParam = { exports: {} };
fileContent(makeRequire(moduleObj, fileName), moduleParam, moduleParam.exports);
result = moduleParam.exports;
} else {
result = fileContent;
}
moduleObj.packageExports[fileName] = result;
return result;
};
if (loadTemp) {
try {
let pack = mw.loader.moduleRegistry['ext.checkUser.tempAccounts'];
makeRequire(pack, pack.script.main)('./initOnHook.js')();
} catch {}
}
if (loadIpInfo) {
try {
let pack = mw.loader.moduleRegistry['ext.ipInfo'];
makeRequire(pack, pack.script.main)('./popup/init.js')();
} catch {}
}
}
if (added || !document.getElementById('p-cactions')) return;
added = true;
let callback = (records, observer) => {
if (records[0].target.classList.contains('diffnow-link-loaded')) {
observer.takeRecords();
observer.disconnect();
clickFirst();
}
};
let clickFirst = () => {
let link = document.querySelector(selector);
if (!link || link.matches('.diffnow-link-loaded, [href*="&diff=cur&"], .consecudiff > a')) {
return;
}
new MutationObserver(callback).observe(link, { attributeFilter: ['class'] });
link.click();
};
mw.util.addPortletLink('p-cactions', '#', 'Expand all diffs').firstElementChild.addEventListener('click', e => {
e.preventDefault();
clickFirst();
});
});
});
// window.smpg?._disable?.();
mw.loader.using('mediawiki.util', async function smoothPager() {
let action = mw.config.get('wgAction');
let isHist = action === 'history';
let isPerma = action === 'view' &&
['oldid', 'diff'].some(s => mw.util.getParamValue(s));
let ns = !isHist && !isPerma && mw.config.get('wgNamespaceNumber');
let isFile = ns === 6;
let isCat = ns === 14;
let cspn = ns === -1 && mw.config.get('wgCanonicalSpecialPageName');
if (!(isHist || isPerma || isFile || isCat || cspn)) return;
let isSearch = cspn === 'Search';
let isWl = cspn === 'Watchlist';
let isRc = cspn === 'Recentchanges' || cspn === 'Recentchangeslinked';
if ((isWl || isRc) && document.body.classList.contains('mw-rcfilters-enabled')) {
return;
}
let isContribs = cspn === 'Contributions' || cspn === 'IPContributions';
let o = {
useRender: isCat || isWl || isRc,
selector: isPerma ? '#bodyContent' :
isFile ? '#mw-imagepage-section-filehistory' :
isCat ? '.mw-category-generated' :
'#mw-content-text',
outerSelectors: [],
linkSelector: isPerma ? '#differences-prevlink, #differences-nextlink, #mw-diff-ntitle1 > strong > a, #mw-diff-otitle1 > strong > a, #mw-revision-nav > a, .fr-diff-to-stable > a' :
isCat ? '#mw-subcategories > a, #mw-pages > a, #mw-category-media > a' :
isWl ? '#ca-nstab-special > a, .mw-watchlist-toollink-active > a, #p-associated-pages .selected > a' :
isRc ? '#ca-nstab-special > a, .rclinks > a, .rcshowhideoption > a, .rclistfrom > a' :
`#ca-nstab-special > a, .mw-pager-navigation-bar > a, .TablePager_nav > .oo-ui-widget-enabled > a, .mw-datatable th > a, .cdx-table-pager .cdx-button--fake-button--enabled, .cdx-table__table__cell--has-sort > a, .mw-prefixindex-nav > a, .mw-allpages-nav > a, .CategoryTreeParents .CategoryTreeLabel, .mw-special-Newpages form div > a, .mw-abusefilter-history-buttons a${isHist ? ', #ca-history > a' : isSearch ? ', .search-types a, .searchdidyoumean a' : ''}`,
canPost: isSearch || [
'AbuseFilter', 'ExpandTemplates', 'TemplateSandbox'
].includes(cspn),
paramRe: isPerma && /^(?:diff|diffonly|direction|oldid)$/ ||
isSearch && /^(?:advancedSearch-current|limit|offset|profile|runsuggestion|search|sort|ns\d+)$/,
reruns: [
'mediawiki.action.history',
'mediawiki.special.search',
'ext.advancedSearch.init',
'mediawiki.special.watchlist',
'mediawiki.special.watchlistedit',
'mediawiki.pager.codex',
'mediawiki.misc-authed-curate',
'mediawiki.misc-authed-ooui',
'mediawiki.misc-authed-pref',
'mediawiki.special.unwatchedPages',
'ext.thanks.corethank',
'ext.flaggedRevs.review',
'ext.gadget.watchlist-notice-core'
],
pages: []
};
if (isContribs) {
o.reruns.push('ext.ipInfo');
}
if (!o.useRender) {
o.outerSelectors.push('#firstHeading', '.mw-indicators');
if (!isPerma && !isFile) {
o.outerSelectors.push('#mw-content-subtitle');
}
}
o.formSelector = isHist && '#mw-history-searchform' ||
isSearch && '#search, #powersearch, #searchform' ||
cspn && `#mw-content-text form${o.canPost ? '': ':not([method="post"])'}`;
if (o.formSelector) {
o.formSelector += ', .cdx-table-pager__limit-form';
}
window.smpg = o;
let getHref = () => location.pathname + location.search;
let getKey = (href, isPost) => {
if (o.paramRe) {
let params = new URLSearchParams();
new URL(href, location.href).searchParams.forEach((v, k) => {
if (o.paramRe.test(k)) {
params.set(k, v);
}
});
params.sort();
return String(params);
}
if (isPost) {
return href + '??' + Date.now();
}
return href;
};
class Page {
constructor(href, key) {
this.href = href || getHref();
this.key = key || getKey(this.href);
this.keys = new Set().add(this.key);
o.pages.push(this);
}
init($response) {
if ($response) {
if (isWl || isRc) {
this.$cont = $(document.querySelector(o.selector).cloneNode())
.removeClass('smoothpager-switching')
.append($response);
} else {
let $cont = $response.filter(o.selector).not('script');
if (!$cont.length) {
$cont = $response.find(o.selector).not('script');
if (!$cont.length) {
throw 'Content not found';
}
}
$cont.find('script').remove();
this.$cont = $($cont[0]);
}
this.attached = 0;
} else {
if (this.$cont) return;
this.$cont = $(document.querySelector(o.selector));
this.attached = 1;
}
this.getConfigAndModules($response);
this.getOuterEls($response);
this.getDocTitle($response);
this.getRev($response);
}
getConfigAndModules($response) {
if (o.useRender) return;
if (!$response) {
this.config = Object.assign({}, window.RLCONF);
}
let $scripts = $response
? $response.filter('script').remove()
: $('head > script, body > script');
$scripts.map(function () {
return this.textContent.match(/\bmw\.config\.set\({.+}\);/g);
}).each((_, s) => {
let obj;
do {
try {
obj = JSON.parse(s.slice(14, -2));
} catch {
let i = s.slice(0, -3).lastIndexOf('});');
if (i === -1) return;
s = s.slice(0, i + 3);
}
} while (!obj);
this.config = Object.assign(this.config || {}, obj);
});
if (!$response) return;
let match = $scripts.first().text().replaceAll('\n', '')
.match(/;RLCONF=({.+});RLSTATE=({.+});RLPAGEMODULES=(\[.+\]);$/);
if (match) {
try {
this.config = Object.assign(this.config || {}, JSON.parse(match[1]));
} catch {}
this.modules = [];
try {
this.modules.push(...Object.keys(JSON.parse(match[2])));
} catch {}
try {
this.modules.push(...JSON.parse(match[3]));
} catch {}
}
this.redirect();
}
setConfig() {
if (!this.config) return;
mw.config.set(this.config);
if (!o.cur.config) return;
Object.entries(o.cur.config).forEach(([k, v]) => {
if (!Object.hasOwn(this.config, k) &&
JSON.stringify(mw.config.get(k)) === JSON.stringify(v)
) {
delete mw.config.values[k];
}
});
}
getOuterEls($response) {
this.outerEls = o.outerSelectors.map(selector => (
$response ? $response.find(selector)[0] : document.querySelector(selector)
)).map(el => el && !this.$cont[0].contains(el) && el);
}
setOuterEls() {
o.outerSelectors.forEach((selector, i) => {
let outer = document.querySelector(selector);
if (!outer) return;
$(outer).before(this.outerEls[i] || outer.cloneNode()).detach();
});
}
getDocTitle($response) {
if (o.useRender) return;
if ($response) {
let $title = $response.filter('title');
if ($title.length) {
this.docTitle = $title.text();
}
} else {
this.docTitle = document.title;
}
}
setDocTitle() {
if (this.docTitle) {
document.title = this.docTitle;
} else if (this.outerEls[0] && o.cur.outerEls?.[0]) {
document.title = document.title.replace(
o.cur.outerEls[0].textContent.trim(),
this.outerEls[0].textContent.trim()
);
}
}
getRev($response) {
if (!isPerma) return;
let params = new URLSearchParams(this.key);
if (params.has('diff')) {
if ($response) {
let dir = params.get('diff') === 'next' ? 'prev' : 'next';
let id = this.config?.[
dir === 'prev' ? 'wgDiffNewId' : 'wgDiffOldId'
];
if (!id) {
id = this.findParam(
'oldid',
dir === 'prev'
? '#mw-diff-ntitle1 > strong > a, #differences-prevlink'
: '#mw-diff-otitle1 > strong > a, #differences-nextlink'
);
}
if (id) {
this.rev = dir === 'prev' ? id : params.get('oldid');
params.set('diff', dir);
params.set('oldid', id);
this.keys.add(String(params));
}
} else {
this.rev = mw.config.get('wgDiffNewId');
let pn = this.findParam(
'title',
'#mw-diff-ntitle1 > strong > a, #differences-nextlink'
);
if (pn === mw.config.get('wgPageName') &&
!this.$cont[0].querySelector('.diff[data-mw-interface] .diff-multi')
) {
params.set('diff', 'prev');
params.set('oldid', this.rev);
this.keys.add(String(params));
let oldId = mw.config.get('wgDiffOldId');
if (oldId) {
params.set('diff', 'next');
params.set('oldid', oldId);
this.keys.add(String(params));
}
}
}
} else {
if ($response) {
if (params.has('direction')) {
let id = this.config?.wgRevisionId;
if (!id) {
id = this.findParam(
'oldid',
'#t-permalink > a, #p-tb .menu__item--page-actions-overflow-permalink',
$response
);
}
if (id) {
this.rev = id;
this.keys.add('oldid=' + id);
o.cur.keys.add(`direction=${
params.get('direction') === 'next' ? 'prev' : 'next'
}&oldid=${id}`);
}
} else {
this.rev = params.get('oldid');
}
} else {
this.rev = mw.config.get('wgRevisionId');
if (params.has('direction')) {
this.keys.add('oldid=' + this.rev);
}
}
}
}
findParam(param, query, $range = this.$cont) {
let search = $range.find(query).prop('search');
return search && mw.util.getParamValue(param, search);
}
updateLinks() {
if (this.rev) {
$('#ca-edit > a').attr('href', mw.util.getUrl(null, {
action: 'edit',
oldid: this.rev
}));
$('#t-permalink > a, #p-tb .menu__item--page-actions-overflow-permalink')
.attr('href', mw.util.getUrl(null, { oldid: this.rev }));
}
$('#ca-nstab-special > a').attr('href', this.href);
$('.printfooter > a').attr('href', location.href).text(location.href);
$('#footer-places-mobileview > a, #mw-mf-display-toggle').attr('href', function () {
let url = new URL(location.href);
url.searchParams.set(
'mobileaction',
mw.util.getParamValue('mobileaction', this.search)
);
return '//' + this.host + url.pathname + url.search;
});
$('#t-urlshortener > a').attr('href', function () {
let params = new URLSearchParams(this.search);
params.set('url', location.href);
return this.pathname + '?' + params;
});
}
redirect() {
if (this.config.wgAction !== mw.config.get('wgAction') ||
this.config.wgPageName?.replace(/\/.*/, '') !== mw.config.get('wgPageName').replace(/\/.*/, '')
) {
location.href = this.href;
throw 'redirect';
}
}
replaceContent(isPopState, isRefresh) {
let oldCont = document.querySelector(o.selector);
if (this.$cont.is(oldCont)) {
throw 'Attempt to replace content with itself';
}
if (!isPopState && !isRefresh) {
this.pushState();
}
$(oldCont).before(this.$cont).detach();
console.log(this.key, o);
this.setOuterEls();
this.setDocTitle();
this.setConfig();
if (isSearch && this.attached) {
let input = this.$cont[0].querySelector('#searchText > input');
if (input) {
input.value = input.defaultValue;
}
}
o.cur = this;
this.loadModules(isPopState);
this.updateLinks();
}
pushState() {
history.pushState({ _smpg: this.key }, '', this.href);
history.replaceState({ _smpg: this.key }, '', this.href);
}
async loadModules(isPopState) {
o.pending = true;
try {
await mw.loader.using(this.modules || []);
} catch {} finally {
o.pending = false;
if (!this.attached) {
this.fireHooks();
this.rerunModules();
}
if (!isPopState) {
this.scroll();
}
this.attached++;
}
}
fireHooks() {
mw.hook('wikipage.content').fire(this.$cont);
this.$cont.find('.diff[data-mw-interface]').each(function () {
mw.hook('wikipage.diff').fire($(this));
});
this.$cont.find('.catlinks[data-mw-interface]').each(function () {
mw.hook('wikipage.categories').fire($(this));
});
mw.hook('htmlform.enhance').fire(this.$cont);
}
rerunModules() {
o.reruns.forEach(m => {
let pack = mw.loader.moduleRegistry[m];
if (!pack || pack.state !== 'ready') return;
if (typeof pack.script === 'function') {
pack.script($, $, mw.loader.require, pack.module);
return;
}
delete mw.loader.moduleRegistry[m];
mw.loader.implement(
m + '@' + pack.version, pack.script, pack.style,
pack.messages, pack.templates, pack.deprecationWarning
);
});
if ((isHist || isContribs || isRc) && window.Twinkle) {
window.Twinkle.rollback();
}
}
scroll() {
let form = o.formSelector && this.$cont[0].querySelector(o.formSelector);
if (form) {
let y = form.getBoundingClientRect().bottom;
if (y < 0) {
window.scrollBy(0, y);
}
} else if (this.$cont[0].getBoundingClientRect().top < 0) {
this.$cont[0].scrollIntoView();
}
}
}
o.cur = new Page();
let getContent = async (href, state, options) => {
if (o.pending) return;
o.aborter?.abort();
let key = state?._smpg || getKey(href, !!options);
let isRefresh = key === o.cur.key;
if (state && isRefresh) return;
let oldCont = document.querySelector(o.selector);
if (!oldCont) {
notify(href, 'No element to replace', 'error');
return;
}
let page = o.pages.find(p => p.keys.has(key));
if ((state || !isRefresh) && page) {
page.href = href;
page.replaceContent(!!state, isRefresh);
return;
}
oldCont.classList.add('smoothpager-switching');
let $throbber = $('<div>').addClass('smoothpager-throbber')
.appendTo(document.body);
if (!isRefresh && o.pages.length === 1) {
o.cur.init();
}
o.aborter = new AbortController();
let url = href;
if (o.useRender) {
url = new URL(href, location.href);
url.searchParams.set('action', 'render');
}
let promise = fetch(url, Object.assign({
signal: o.aborter.signal
}, options));
if (notif) {
notif.close();
notif = null;
}
try {
let response = await (await promise).text();
if (page) {
page.href = href;
} else {
page = new Page(href, key);
}
page.init($($.parseHTML(response, !o.useRender)));
page.replaceContent(!!state, isRefresh);
} catch (e) {
if (e.name === 'AbortError') {
if (state && o.cur) {
o.cur.pushState();
}
} else if (e === 'redirect') {
notify(href, 'Redirecting...');
} else {
notify(href, e || `Couldn't load the page`, 'error');
console.error(e);
}
} finally {
o.aborter = null;
oldCont.classList.remove('smoothpager-switching');
$throbber.remove();
}
};
let notif;
let notify = async (href, msg, type) => {
notif = await mw.notify([
document.createTextNode(msg),
$('<p>').append(
$('<a>').attr({
href: href,
target: '_blank'
}).text('Open it in a new tab')
)[0]
], {
autoHideSeconds: 'long',
tag: 'smoothpager',
type: type
});
};
let onClick = function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
!this.matches(o.linkSelector) || this.origin !== location.origin
) {
return;
}
e.preventDefault();
getContent(this.pathname + this.search);
};
let onKeyDown = e => {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
e.target.closest(':read-write')
) {
return;
}
switch (e.which) {
case 27:
o.aborter?.abort();
break;
case 110:
case 190:
e.preventDefault();
getContent(getHref());
}
};
let onPopState = e => {
let state = e.originalEvent.state;
if (state && !state._smpg) return;
history.replaceState(state, '', location.href);
getContent(getHref(), state || {});
};
let onSubmit = e => {
if (e.originalEvent && (
e.originalEvent.defaultPrevented || !e.originalEvent.isTrusted
) || !o.canPost && e.target.method !== 'get' ||
!e.target.matches(o.formSelector)
) {
return;
}
let path = e.target.getAttribute('action');
let isScript = path === mw.config.get('wgScript');
let comps = [mw.config.get('wgPageName')];
if (mw.config.get('wgNamespaceNumber') === -1 && comps[0].includes('/')) {
comps.push(comps[0].replace(/\/.*/, ''));
}
comps.push((comps[1] || comps[0]) + '/');
if (!isScript && !comps.some(c => mw.util.getUrl(c) === path)) return;
let formData = new FormData(e.target);
if (isScript && !comps.includes(formData.get('title').replaceAll(' ', '_'))) {
return;
}
e.preventDefault();
e.stopPropagation();
if (isSearch && e.target.id !== 'searchform') {
formData.set('search', OO.ui.infuse($('#searchText')).getValue());
} else if (e.target.method === 'post') {
let submitter = e.originalEvent?.submitter;
if (submitter?.name) {
formData.append(submitter.name, submitter.value);
}
getContent(path, null, {
method: 'POST',
headers: { 'Content-Type': e.target.enctype },
body: e.target.enctype === 'multipart/form-data'
? formData
: new URLSearchParams(formData)
});
return;
}
getContent(path + '?' + new URLSearchParams(formData));
};
let setPortlet = text => {
if (!o.portletLink) return;
$(o.portletLink).find('*').addBack().contents().each(function () {
if (this.nodeType === 3 && this.textContent.trim()) {
this.textContent = text;
return false;
}
});
};
o._enable = () => {
o.enabled = true;
let $body = $(document.body).on('click', 'a', onClick);
if (o.formSelector) {
$body.on('submit', onSubmit);
}
$body.parent().on('keydown', onKeyDown);
$(window).on('popstate', onPopState);
if (o.css) {
o.css.disabled = false;
}
if (isPerma) {
mw.trackSubscribe('counter.MediaWiki.RevisionSlider.event.init', o._disable);
}
setPortlet('Disable SmoothPager');
};
o._disable = () => {
o.enabled = false;
$(document.body).off('click', onClick).off('submit', onSubmit)
.parent().off('keydown', onKeyDown);
$(window).off('popstate', onPopState);
o.css.disabled = true;
mw.trackUnsubscribe(o._disable);
setPortlet('Enable SmoothPager');
};
o._enable();
o.css = mw.loader.addStyleTag(`${o.linkSelector},
.cdx-table__table__sort-button {
color: #008064 !important;
}
:is(${o.linkSelector}, .cdx-table__table__sort-button):visited {
color: #006400 !important;
}
:is(${o.linkSelector}, .cdx-table__table__sort-button):active {
color: #faa700 !important;
}
.cdx-table-pager .cdx-button--fake-button--enabled > .cdx-button__icon {
background-color: #14866d;
}
.smoothpager-switching {
opacity: 0.75;
}
.smoothpager-throbber {
width: 20%;
height: 0.5vh;
position: fixed;
top: 0;
left: 0;
background-color: var(--background-color-progressive, #36c);
transform: translate(-100%);
animation: smoothpager-throbber 1s infinite linear;
}
@keyframes smoothpager-throbber {
to {
transform: translate(700%);
}
}
@media (prefers-reduced-motion: reduce) {
.smoothpager-throbber {
animation: smoothpager-throbber 1s infinite steps(10,end) !important;
width: 40%;
}
}${o.formSelector ? `
:is(${o.formSelector}) input[type="submit"],
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
color: var(--color-inverted, #fff);
background-color: #14866d;
border-color: #14866d;
}
:is(${o.formSelector}) input[type="submit"]:hover,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover {
background-color: #00af89;
border-color: #00af89;
}
:is(${o.formSelector}) input[type="submit"]:focus,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
border-color: #14866d;
}
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
box-shadow: inset 0 0 0 1px #14866d, inset 0 0 0 2px #fff;
}
:is(${o.formSelector}) input[type="submit"]:active,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active {
background-color: #0e725a;
border-color: #0e725a;
box-shadow: none;
}` : ''}`);
await $.ready;
if (!document.getElementById('p-cactions')) return;
o.portletLink = mw.util.addPortletLink('p-cactions', '#', 'Disable SmoothPager').firstElementChild;
o.portletLink.addEventListener('click', e => {
e.preventDefault();
o[o.enabled ? '_disable' : '_enable']();
});
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], function smartDiff() {
mw.loader.addStyleTag('.smartdiff-link.extiw, .smartdiff-link.external{color:var(--color-progressive,#36c)} .smartdiff-link.extiw:visited, .smartdiff-link.external:visited{color:#795cb2} .smartdiff-link.extiw:active, .smartdiff-link.external:active{color:#faa700}');
class SmartDiff {
constructor($diff) {
this.$diff = $diff;
this.isSpecial = mw.config.get('wgNamespaceNumber') === -1;
this.isView = mw.config.get('wgAction') === 'view' &&
new URLSearchParams(location.search).get('diffonly') !== '1';
this.magicWords = [
'!', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME',
'CURRENTDOW', 'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1',
'CURRENTMONTHABBREV', 'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN',
'CURRENTTIME', 'CURRENTTIMESTAMP', 'CURRENTVERSION', 'CURRENTWEEK',
'CURRENTYEAR', 'DEFAULTCATEGORYSORT:', 'DEFAULTSORT:', 'DEFAULTSORTKEY:',
'DISPLAYTITLE:', 'filepath:', 'formatnum:', 'FULLPAGENAME',
'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'int:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2',
'LOCALDAYNAME', 'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1',
'LOCALMONTHABBREV', 'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME',
'LOCALTIMESTAMP', 'LOCALWEEK', 'LOCALYEAR', 'msg:', 'msgnw:',
'NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:', 'NAMESPACENUMBER',
'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS', 'NUMBEROFARTICLES',
'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES', 'NUMBEROFUSERS',
'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:', 'PAGESINCATEGORY:',
'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2', 'REVISIONDAY2:',
'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:',
'TALKSPACEE', 'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
if (window.smartdiffMagicWords) {
this.magicWords.push(...window.smartdiffMagicWords);
}
try {
this.subNs = mw.config.get('wgVisualEditorConfig').namespacesWithSubpages;
} catch (e) {}
if (!this.subNs) {
this.subNs = Object.keys(mw.config.get('wgFormattedNamespaces'))
.map(k => Number(k)).filter(ns => ![0, 6, 8].includes(ns));
}
this.re = /((?:\[(?:<[^>]*>)?\[|(?<!{(?:<[^>]*>)?){(?:<[^>]*>)?{(?:<[^>]*>)?(?:(?:#(?:<[^>]*>)?invoke|(?:safe)?subst|msg(?:nw)?|raw|int)(?:<[^>]*>)?:)?)(?:\s*(?:<[^>]*>)?<(?:<[^>]*>)?tvar(?:<[^>]*>)?\s(?!>).*?>)?\s*)((?:(?!&[gl]t;)[^\[\]{|}])+?)(?=\s*(?:(?:<[^>]*>)?<(?:<[^>]*>)?\/(?:<[^>]*>)?tvar(?:<[^>]*>)?>(?:<[^>]*>)?\s*)?(?:\||\](?:<[^>]*>)?\]|}(?:<[^>]*>)?}|$))/g;
this.headRe = /^((?:(?:<[^>]*>)*=){1,6}(?:<[^>]*>)?\s*)((?:(?!&[gl]t;).)+?)(?=\s*(?:(?:<[^>]*>)?=){1,6}(?:<[^>]*>|\s)*(?:<|$))/g;
this.galleryRe = /^(\s*)((?:(?!&[gl]t;)[^\[\]{|}])+\.(?:<[^>]*>)?(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf)?)(?=\s*(?:(?:<[^>]*>)?(?:<[^>]*>)?\s*)?(?:\||$))/gi;
this.urlRe = /(\[(?:<[^>]*>)?(?=.+\]))?((\bhttps?(?:<[^>]*>)?:)?(?:<[^>]*>)?\/(?:<[^>]*>)?\/(?:<[^>]*>|(?!&[gl]t;)[^\s"<>\[\]{|}])+)/g;
if (window.smartdiffTemplates) {
this.tempRe = /( data-smartdiff-temp="(\d+)">[^{|}]+)(\|(?:(?!&[gl]t;)[^\[\]{}]|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?})+)(?=}(?:<[^>]*>)?}|$)/g;
this.tempSubRe = /((?:\s|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?}[^<>|]*|<[^>]*>)*(?:\|(?:\s|(?:<[^>]*>)|\d+(?:\s|<[^>]*>)*=|[^\d<=>|](?:[^<=>|]|<[^>]*>)*=(?:[^<=>|]|<[^>]*>)*\|?)*|$))/;
this.templates = window.smartdiffTemplates;
}
this.side = 'old';
$diff.find('.diff-deletedline > div').get().forEach(this.processDiv);
this.side = 'new';
$diff.find('.diff-addedline > div').get().forEach(this.processDiv);
let $contexts = $diff.find('.diff-context > div');
$contexts.each((i, div) => {
if (i % 2) {
this.side = 'new';
if (this.propUsed && this.getProp() !== this.getProp('pn', 'old')) {
this.processDiv(div);
} else {
$contexts.eq(i).replaceWith($contexts.eq(i - 1).clone());
}
} else {
this.side = 'old';
this.propUsed = false;
this.processDiv(div);
}
});
this.links = {};
$diff.find('.smartdiff-link:not(.external)').each((i, link) => {
let title = link.title;
if (!title) return;
if (!this.links.hasOwnProperty(title)) {
this.links[title] = [];
}
this.links[title].push(link);
});
this.query(Object.keys(this.links).slice(0, 500));
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
}
processDiv = div => {
if (div.querySelector('a[href]')) return;
let origHtml = div.innerHTML;
let newHtml = origHtml.replace(this.urlRe, this.urlRep)
.replace(this.galleryRe, this.galleryRep)
.replace(this.re, this.rep).replace(this.headRe, this.headRep);
if (this.tempRe) {
newHtml = newHtml.replace(this.tempRe, this.tempRep);
}
if (newHtml === origHtml) return;
newHtml = newHtml.replace(/<(ins|del)(?: [^>]+)?><\/\1>/g, '');
let $newDiv = $('<div>').html(newHtml);
if (this.detectErrors($newDiv, newHtml, origHtml, div)) return;
div.textContent = '';
$newDiv.contents().appendTo(div);
};
rep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
let t = mw.Title.newFromText(s), isTemp;
if (t) {
if ($1.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s[0] === '/') {
if (this.subNs.includes(this.getProp('ns'))) {
t = mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
} else if ($1[0] === '{') {
t.namespace = 10;
}
} else if ($1[0] === '{') {
if (s[0] === '#') {
return $0;
}
if ($1.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (!t.namespace && s[0] !== ':') {
if (!$1.includes('msg') && !$1.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && this.magicWords.includes(match[0])) {
return $0;
}
}
t.namespace = 10;
isTemp = true;
}
} else if ((this.isSpecial || !this.isView) && s[0] === '#') {
t.title = this.getProp();
}
} else if (s.startsWith('../') && this.subNs.includes(this.getProp('ns'))) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
t = mw.Title.newFromText(sub ? sup + '/' + sub : sup);
}
}
if (!t) {
return $0;
}
let attrs = {
class: 'smartdiff-link',
href: t.getUrl()
};
if (this.isSpecial || !this.isView || s[0] !== '#') {
attrs.title = t.toText();
}
if (isTemp && this.tempRe) {
let name = t.getMainText();
let idx = this.templates.findIndex(temp => temp.names.includes(name));
if (idx !== -1) {
attrs['data-smartdiff-temp'] = idx;
}
}
return pre + $('<a>').attr(attrs).html(mid)[0].outerHTML + post;
};
stripTags(s, decode, pre = '', post = '') {
let mid = s, tags = s.match(/<\/?(?:ins|del)(?: [^>]+)?>/g);
s = $($.parseHTML(s.replace(/&/g, '&'))).text();
if (decode) {
try {
s = decodeURIComponent(s);
} catch (e) {}
}
if (tags) {
if (tags[0][1] === '/') {
pre += tags[0];
mid = `<${tags[0].slice(2, 5)} class="diffchange diffchange-inline">` + mid;
}
let lastTag = tags.pop();
if (lastTag[1] !== '/') {
mid += `</${lastTag.slice(1, 4)}>`;
post = lastTag + post;
}
}
return [s, pre, mid, post];
}
headRep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
s = s.replace(/'''(.+?)'''|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>/gi, '$1')
.replace(/''(.+?)''/g, '$1')
.replace(/^_+|_+$/g, '');
let t = mw.Title.newFromText(
`${this.isSpecial || !this.isView ? this.getProp() : ''}#${s}`
);
if (!t) {
return $0;
}
let attrs = {
class: 'smartdiff-link',
href: t.getUrl()
};
if (this.isSpecial || !this.isView) {
attrs.title = t.toText();
}
return pre + $('<a>').attr(attrs).html(mid)[0].outerHTML + post;
};
galleryRep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
let t = mw.Title.newFromText(s, 6);
if (!t) {
return $0;
}
return pre + $('<a>').attr({
class: 'smartdiff-link',
href: t.getUrl(),
title: t.toText()
}).html(mid)[0].outerHTML + post;
};
urlRep = ($0, $1, $2, $3) => {
let main = $2, trail;
if (!$1) {
if (!$3) {
return $0;
}
let re = main.includes('(')
? /[!,.:;?](?:<[^>]*>)?$/
: /[!),.:;?](?:<[^>]*>)?$/;
let match = main.match(re);
if (match &&
!/&(?:;(?:<[^>]*>)?(?:[a-z]+|(?:#(?:<[^>]*>)?(?:x[\da-f]|\d+)))(?:<[^>]*>)?)?;$/i.test(main)
) {
trail = match[0];
main = main.slice(0, -trail.length);
}
}
let [url, pre, mid, post] = this.stripTags(main);
if ($1) {
pre = $1 + pre;
} else if (trail) {
post += trail;
}
return pre + $('<a>').attr({
class: 'smartdiff-link external',
href: url,
rel: 'nofollow'
}).html(mid)[0].outerHTML + post;
};
tempRep = ($0, $1, $2, $3) => {
if ($3.includes('<a class="smartdiff-link')) {
return $0;
}
let temp = this.templates[$2];
return $1 + $3.split(this.tempSubRe).map((os, i) => {
if (!os || i % 2) {
return os;
}
let j = i / 2;
if (j < temp.start || j > temp.end ||
temp.skipOdd && j % 2 || temp.skipEven && j % 2 === 0
) {
return os;
}
let [s, pre, mid, post] = this.stripTags(os, true);
if (temp.prefix) {
s = temp.prefix + s;
}
if (temp.suffix) {
s += temp.suffix;
}
let t = temp.forceNs
? mw.Title.makeTitle(temp.namespace, s)
: mw.Title.newFromText(s, temp.namespace);
if (!t) {
return os;
}
let params = (j >= temp.noRedirectStart || j <= temp.noRedirectEnd) &&
{ redirect: 'no' };
return pre + $('<a>').attr({
class: 'smartdiff-link',
href: t.getUrl(params),
title: t.toText()
}).html(mid)[0].outerHTML + post;
}).join('');
};
getProp(n = 'pn', side = this.side) {
this.propUsed = true;
if (this[side]) {
if (this[side][n]) {
return this[side][n];
}
} else {
this[side] = {};
let link = this.$diff[0].querySelector(
side === 'old'
? '#mw-diff-otitle1 a, #differences-prevlink'
: '#mw-diff-ntitle1 a, #differences-nextlink'
);
if (link) {
let pn = mw.util.getParamValue('title', link.search);
this[side].pn = pn;
this[side].ns = mw.Title.newFromText(pn).namespace;
return this[side][n];
}
}
if (this[n]) {
return this[n];
}
if (this.isSpecial) {
this.pn = '';
this.ns = 0;
} else {
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
}
return this[n];
}
query(titles) {
if (!titles.length) return;
new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
}).then(response => {
let query = response && response.query;
if (!query) return;
let data = {};
(query.pages || []).forEach(page => {
let obj = { classes: page.linkclasses || [] };
if (page.missing && !page.known) {
obj.classes.push('new');
obj.params = { action: 'edit', redlink: 1 };
}
data[page.title] = obj;
});
(query.interwiki || []).forEach(interwiki => {
data[interwiki.title] = {
classes: ['extiw'],
url: interwiki.url
};
});
(query.normalized || []).forEach(entry => {
if (!data.hasOwnProperty(entry.to)) return;
let obj = data[entry.to];
obj.canonical = entry.to;
if (!obj.url) {
obj.url = mw.util.getUrl(entry.to, obj.params);
}
data[entry.from] = obj;
});
Object.entries(data).forEach(([title, obj]) => {
if (!this.links.hasOwnProperty(title)) return;
let $links = $(this.links[title]).addClass(obj.classes)
.attr('title', obj.canonical);
if (obj.url) {
$links.attr('href', function () {
return obj.url + this.hash;
});
}
});
this.query(titles.slice(50));
});
}
detectErrors($newDiv, newHtml, origHtml, div) {
let comp = $newDiv.html();
if (comp !== newHtml) {
console.warn(
'SmartDiff syntax error at:\n',
div,
`\nNew HTML:\n${newHtml}\nCompared against:\n${comp}`
);
this.hasError = true;
return true;
}
let $comp = $newDiv.clone();
$comp.find('.smartdiff-link').contents().unwrap();
comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
console.warn(
'SmartDiff mutation error at:\n',
div,
`\nOriginal HTML:\n${origHtml}\nCompared against:\n${comp}`
);
this.hasError = true;
return true;
}
}
}
mw.hook('wikipage.diff').add($diff => {
new SmartDiff($diff);
});
});
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopySectLink.js&action=raw&ctype=text/javascript', 's');
mw.loader.using([
'ext.visualEditor.desktopArticleTarget.init', 'mediawiki.storage'
], function ipaInput() {
if (!mw.libs.ve.isVisualAvailable &&
!['edit', 'submit'].includes(mw.config.get('wgAction'))
) {
return;
}
mw.loader.addStyleTag(`.oo-ui-icon-schwa{background-image:url("data:image/svg+xml,%3Csvg width='20' height='20' version='1.1' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m0 3v14h4v-2h-2v-10h2v-2zm16 0v2h2v10h-2v2h4v-14zm-6 2c-2.41 0-4.43 1.73-4.9 4h2.08c0.41-1.17 1.5-2 2.82-2 1.67 0 3 1.33 3 3h-8c0 2.75 2.25 5 5 5 2.75 0 5-2.25 5-5 0-2.75-2.25-5-5-5zm-2.59 6.5h5.18c-0.516 0.895-1.47 1.5-2.59 1.5-1.12 0-2.07-0.605-2.59-1.5z'/%3E%3C/svg%3E")}`);
let clicked;
let openDialog = () => {
if (clicked) {
if (window.ipaInputDialog) {
window.ipaInputDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox4.js&action=raw&ctype=text/javascript');
mw.loader.using([
'jquery.textSelection', 'oojs-ui-windows', 'oojs-ui-widgets',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-editing-core',
'oojs-ui.styles.icons-editing-advanced'
]);
};
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.wikiEditor('addToToolbar', {
section: 'main',
group: 'insert',
tools: {
ipainput: {
label: 'IPAInput',
type: 'button',
oouiIcon: 'schwa',
action: { type: 'callback', execute: openDialog }
}
}
});
});
mw.hook('ve.loadModules').add(addPlugin => {
addPlugin(() => {
ve.ui.IpaInputCommand = function VeUiIpaInputCommand() {
ve.ui.IpaInputCommand.super.call(this, 'ipaInput');
};
OO.inheritClass(ve.ui.IpaInputCommand, ve.ui.Command);
ve.ui.IpaInputCommand.prototype.execute = () => {
openDialog();
return true;
};
ve.ui.commandRegistry.register(new ve.ui.IpaInputCommand());
ve.ui.IpaInputTool = function VeUiIpaInputTool() {
ve.ui.IpaInputTool.super.apply(this, arguments);
};
OO.inheritClass(ve.ui.IpaInputTool, ve.ui.Tool);
ve.ui.IpaInputTool.static.name = 'ipaInput';
ve.ui.IpaInputTool.static.group = 'insert';
ve.ui.IpaInputTool.static.icon = 'schwa';
ve.ui.IpaInputTool.static.title = 'IPA';
ve.ui.IpaInputTool.static.commandName = 'ipaInput';
ve.ui.toolFactory.register(ve.ui.IpaInputTool);
});
});
mw.requestIdleCallback(() => {
let expiry = mw.storage.get('_EXPIRY_ipainput-cache');
if (!expiry) return;
$.get(
'//en.wikipedia.org/api/rest_v1/page/title/Module%3AIPA%2Fdata'
).then(response => {
if (Date.parse(response.items[0].timestamp) / 1000 > expiry - 604800) {
mw.storage.remove('ipainput-cache');
}
});
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function wiktIpa() {
mw.loader.addStyleTag(`.oo-ui-icon-wiktionary{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath d='M14.95 1c-.15 0-.3 0-.45.03L2.9 2.9a2.26 2.26 0 0 0-1.87 2.6L2.9 17.1a2.26 2.26 0 0 0 2.6 1.86l11.6-1.88a2.26 2.26 0 0 0 1.86-2.6L17.1 2.9A2.27 2.27 0 0 0 14.95 1zm-.03.9c.63.03 1.17.49 1.28 1.14l1.88 11.6c.12.75-.37 1.43-1.12 1.56l-11.6 1.88a1.34 1.34 0 0 1-1.56-1.12L1.92 5.36A1.34 1.34 0 0 1 3.04 3.8l11.6-1.88.28-.02zm.7 2.61-2.83.46.07.39c.6-.09.95-.14 1.08.36.1.6-.91 6.53-.91 6.53s-2.87-5.16-2.98-5.87c-.02-.34.02-.64.86-.7l-.06-.4-3.64.6.07.38c.5-.15 1.01.02 1.43.82l.7 1.33-.72 4.54s-2.93-5.3-3.03-5.9c-.07-.5.45-.64.8-.66l-.06-.38-3.46.56.06.39c.24-.1.84-.07 1.07.32.07.09 4.54 8.44 4.54 8.44l.33-.05 1.02-6.24 2.98 5.59.36-.06s1.42-9.14 1.48-9.3c.05-.26.28-.71.9-.76l-.07-.39z'/%3E%3C/svg%3E")}`);
let clicked, dialog, input, $result;
let openDialog = async context => {
if (clicked) {
if (dialog) {
let selection = context.$textarea.textSelection('getSelection');
if (selection) {
input.setValue(selection);
}
if ($result) {
$result.prev().addBack().remove();
$result = null;
}
dialog.open({
actions: [{ label: 'Close', flags: ['safe', 'close'] }]
});
}
return;
}
clicked = true;
await mw.loader.using([
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.ForeignApi',
'mediawiki.util'
]);
let response = await new mw.ForeignApi('//en.wiktionary.org/w/api.php').get({
action: 'query',
generator: 'search',
gsrsearch: 'deepcat:Pronunciation_templates_by_language',
gsrnamespace: 10,
gsrlimit: 'max',
gsrsort: 'none',
formatversion: 2
});
let winMan = new OO.ui.WindowManager();
dialog = new OO.ui.MessageDialog();
winMan.addWindows([dialog]);
let items = response.query.pages
.map(p => p.title.slice(9))
.sort(Intl.Collator('en-u-kn-true').compare)
.map(s => new OO.ui.MenuOptionWidget({ label: s }));
let dropdown = new OO.ui.DropdownWidget({
$overlay: dialog.$overlay,
menu: { items }
});
let $doc = $('<p>');
dropdown.getMenu().on('choose', mw.util.debounce(async item => {
let title = 'Template:' + item.getLabel();
let $link = $('<a>').attr({
href: '//en.wiktionary.org/wiki/' + title,
target: '_blank',
title: title
}).text('documentation');
$doc.empty().append('Loading ', $link, '...');
try {
let data = await $.get(
'//en.wiktionary.org/api/rest_v1/page/html/' +
encodeURIComponent(title + '/documentation')
);
let text = $($.parseHTML(data)).find('p').first().text()
.replace(/\. .*/, '.');
$doc.text(text + ' (').append($link.text('read more'), ')');
dialog.updateSize();
} catch {
$doc.empty().append('Failed to load ', $link);
}
}, 100)).selectItem(items[0]);
input = new OO.ui.TextInputWidget({
autocomplete: false,
value: context.$textarea.textSelection('getSelection') ||
mw.config.get('wgTitle')
});
let button = new OO.ui.ButtonWidget({
disabled: !input.getValue(),
label: 'Get',
flags: ['primary', 'progressive']
}).on('click', async () => {
button.setDisabled(true);
let template = dropdown.getMenu().findSelectedItem().getLabel();
let text = input.getValue();
try {
let data = await $.post('//en.wiktionary.org/api/rest_v1/transform/wikitext/to/html', {
wikitext: `{{${template}|1=${text}}}`,
body_only: true
});
if ($result) {
$result.children().remove();
} else {
$result = $('<div>').text('Result:')
.insertAfter(fieldset.$element)
.before('<hr>');
}
$result.append($.parseHTML(data))
.find('.mw-collapsible').makeCollapsible().end()
.find('[id], [about]').removeAttr('id about').end()
.find('a').attr('target', '_blank')
.filter('[href^="./"]').attr('href', (_, href) => (
'//en.wiktionary.org/wiki' + href.slice(1)
));
dialog.updateSize();
} catch {} finally {
button.setDisabled();
}
});
input.on('change', value => {
button.setDisabled(!value);
}).connect(button, { enter: ['emit', 'click'] });
let fieldset = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(dropdown, {
label: 'Template:',
align: 'top'
}),
new OO.ui.FieldLayout(input, {
label: 'Input:',
align: 'top'
}),
new OO.ui.FieldLayout(button)
]
});
dropdown.$element.after($doc);
dialog.text.$element.append(fieldset.$element/*.on('keydown', e => {
e.stopPropagation();
})*/);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
dialog.open({
actions: [{ label: 'Close', flags: ['safe', 'close'] }]
});
};
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.wikiEditor('addToToolbar', {
section: 'main',
group: 'insert',
tools: {
wiktipa: {
label: 'WiktIPA',
type: 'button',
oouiIcon: 'wiktionary',
action: { type: 'callback', execute: openDialog }
}
}
});
});
}());
window.scripttesterSkipWarning = true;
mw.loader.using(['mediawiki.util', 'mediawiki.storage'], async function scriptTester() {
let get = () => new Set(mw.storage.getObject('scripttester'));
if (mw.storage.get('scripttester')) {
let unloading;
window.addEventListener('beforeunload', () => {
unloading = true;
});
get().forEach(async s => {
let isCss = /\.css$/i.test(s);
let url = /^(https?:)?\/\/./.test(s) ? s : mw.util.getUrl(s, {
action: 'raw',
ctype: isCss ? 'text/css' : 'text/javascript'
});
if (isCss) {
mw.loader.load(url, 'text/css');
return;
}
try {
await mw.loader.getScript(url);
} catch (e) {
if (unloading) {
console.warn(e);
return;
}
mw.notify('Failed to load temporarily installed ' + s, { type: 'error' });
}
});
} else if (!window.scripttesterSkipWarning) {
await mw.loader.using('oojs-ui-windows');
if (await OO.ui.confirm(
'You take full responsibility for any consequences arising from using ScriptTester.'
)) {
mw.storage.setObject('scripttester', []);
}
}
await $.ready;
if (!document.getElementById('p-tb')) return;
let css = mw.loader.addStyleTag('.scripttester-dialog .oo-ui-checkboxMultiselectWidget{margin:0.5em 0;word-break:break-all} .scripttester-dialog .oo-ui-flaggedElement-destructive{float:right}');
let linksShown = mw.config.get('wgNamespaceNumber') > 0 &&
mw.config.get('wgAction') === 'view';
let updateLinks = (s, unins) => {
if (!linksShown) return;
$(`.scripttester-link[data-scripttester="${s}"]`)
.toggleClass('scripttester-installed', !unins);
};
let dialog, multiselect, addButton, removeButton, clearButton;
let openDialog = () => {
if (!dialog) {
dialog = new OO.ui.MessageDialog({ classes: ['scripttester-dialog'] });
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
multiselect = new OO.ui.CheckboxMultiselectWidget().on('select', () => {
removeButton.setDisabled(!multiselect.findSelectedItems().length);
});
addButton = new OO.ui.ButtonWidget({
label: 'Add'
}).on('click', async () => {
dialog.toggle(false);
let s = (await OO.ui.prompt('Add a script', {
textInput: { placeholder: 'Script page name or URL' }
})).trim();
if (!s) return;
if (!/^(https?:)?\/\/./.test(s) && !mw.Title.newFromText(s)) {
await OO.ui.alert(`"${s}" does not appear to be a valid page name or URL.`);
dialog.toggle(true);
updateDialog();
return;
}
mw.storage.setObject('scripttester', [...get().add(s)]);
updateLinks(s);
});
removeButton = new OO.ui.ButtonWidget({
label: 'Remove'
}).on('click', () => {
let set = get();
multiselect.findSelectedItems().forEach(item => {
let s = item.getLabel();
set.delete(s);
updateLinks(s, true);
});
mw.storage.setObject('scripttester', [...set]);
updateDialog();
});
clearButton = new OO.ui.ButtonWidget({
label: 'Clear',
flags: 'destructive'
}).on('click', async () => {
dialog.toggle(false);
if (!(await OO.ui.confirm('Uninstall all scripts?'))) {
dialog.toggle(true);
return;
}
mw.storage.setObject('scripttester', []);
if (linksShown) {
$('.scripttester-installed').removeClass('scripttester-installed');
}
dialog.toggle(true);
updateDialog();
});
dialog.text.$element.append(
multiselect.$element,
new OO.ui.ButtonGroupWidget({
items: [addButton, removeButton]
}).$element,
clearButton.$element
);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
}
updateDialog();
dialog.open({
message: 'Temporarily installed scripts:',
actions: [{ label: 'Done', flags: ['safe', 'close'] }]
});
};
let updateDialog = () => {
let set = get();
multiselect.clearItems().addItems(
[...set].map(s => new OO.ui.CheckboxMultioptionWidget({ label: s }))
);
removeButton.setDisabled(true);
clearButton.toggle(set.size);
dialog.updateSize();
updatePortlet(set.size);
};
let updatePortlet = count => {
$(portletLink).find('*').addBack().contents().each(function () {
if (this.nodeType === 3 && this.textContent.trim()) {
this.textContent = `Temporarily installed scripts (${count})`;
return false;
}
});
};
let portletLink = mw.util.addPortletLink('p-tb', '#', `Temporarily installed scripts (${get().size})`)
.firstElementChild;
portletLink.addEventListener('click', e => {
e.preventDefault();
mw.loader.using(['oojs-ui-windows', 'mediawiki.Title'], openDialog);
});
window.addEventListener('storage', e => {
if (e.key === 'scripttester') {
updatePortlet(get().size);
}
});
if (!linksShown) return;
css.textContent += ' .scripttester{font-size:85%;user-select:none} .scripttester::before{content:" "} .scripttester-link::after{content:"[+]"} .scripttester-installed::after{content:"[−]"} #firstHeading > .scripttester{font-size:47%}';
let linkHandler = function () {
let s = this.dataset.scripttester;
let unins = this.classList.contains('scripttester-installed');
let set = get();
set[unins ? 'delete' : 'add'](s);
let success = mw.storage.setObject('scripttester', [...set]);
if (success) {
mw.notify((unins ? 'Uninstalled ' : 'Installed ') + s, {
tag: 'scripttester'
});
updateLinks(s, unins);
updatePortlet(set.size);
} else {
mw.notify(`Couldn't ${unins ? 'un' : ''}install ${s}`, {
tag: 'scripttester',
type: 'error'
});
}
};
if ([2, 4, 8].includes(mw.config.get('wgNamespaceNumber')) &&
['javascript', 'css'].includes(mw.config.get('wgPageContentModel'))
) {
let s = mw.config.get('wgPageName').replaceAll('_', ' ');
$('<span>').addClass('scripttester').append(
$('<a>').attr({
class: 'scripttester-link' + (get().has(s) ? ' scripttester-installed' : ''),
tabindex: 0,
role: 'button',
'data-scripttester': s
}).text('\u200e').on('click', linkHandler)
).appendTo(document.getElementById('firstHeading'));
return;
}
mw.hook('wikipage.content').add($content => {
let set = get();
let ns = mw.config.get('wgFormattedNamespaces');
let re = new RegExp(`^(${ns[2]}|${ns[4]}|${ns[8]}):.+\\.([Cc][Ss]|[Jj])[Ss]$`);
$content.find('a:not(.external, .new)').after(function () {
let s = this.title;
if (!s || !re.test(s)) return;
return $('<span>').addClass('scripttester').append(
$('<a>').attr({
class: 'scripttester-link' + (set.has(s) ? ' scripttester-installed' : ''),
tabindex: 0,
role: 'button',
'data-scripttester': s
}).text('\u200e').on('click', linkHandler)
);
});
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.on('keydown', async e => {
if (e.which !== 72 || e.shiftKey || e.altKey || [e.ctrlKey, e.metaKey].filter(Boolean).length !== 1) return;
e.preventDefault();
let context = $textarea.data('wikiEditorContext');
context.api.openDialog(context, 'search-and-replace');
await mw.loader.using('jquery.textSelection');
let tb = document.getElementById('wikieditor-toolbar-replace-search');
let sel = $textarea.textSelection('getSelection');
if (sel) {
tb.value = sel;
}
tb.focus();
});
$(document.body).on('dialogclose', '#wikieditor-toolbar-replace-dialog', () => {
$textarea[0].focus();
});
});
mw.config.get('wgNamespaceNumber') &&
mw.config.get('wgAction') !== 'history' &&
(function catChangeHighlighter() {
let run;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-changeslist-line.mw-changeslist-src-mw-categorize').each(function () {
let text = this.querySelector('.comment').textContent;
if (text.includes(' added ')) {
this.classList.add('catchangehighlighter-addition');
} else if (text.includes(' removed ')) {
this.classList.add('catchangehighlighter-removal');
}
if (run) return;
run = true;
mw.loader.addStyleTag('.catchangehighlighter-addition :is(span, td) > .comment{background:#f5fff5} .catchangehighlighter-removal :is(span, td) > .comment{background:#fff5f5}');
});
});
}());
(mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') !== 'view' ||
[-1, 14].includes(mw.config.get('wgNamespaceNumber'))) &&
(function diffFontSwitcher() {
mw.loader.addStyleTag('.diff-lineno{cursor:pointer}');
$(document.body).on('click keydown', '.diff-lineno', function (e) {
if (e.type === 'keydown' && (
e.which !== 13 && e.which !== 32 ||
e.ctrlKey || e.shiftKey || e.metaKey || e.altKey
)) {
return;
}
e.preventDefault();
this.closest('.diff').classList.toggle('difffontswitcher-enabled');
});
mw.hook('wikipage.diff').add($diff => {
$diff.find('.diff-lineno').attr({ tabindex: 0, role: 'button' });
});
}());
mw.trackSubscribe('resourceloader.exception', (topic, data) => {
mw.notify(data.exception, {
autoHide: false,
title: `Exception in ${data.source} in module ${data.module}`,
type: 'warn'
});
});
mw.config.get('skin') === 'vector-2022' &&
$(document).one('click', '.mw-interlanguage-selector', async () => {
await mw.loader.using('ext.uls.mediawiki');
$.fn.uls.Constructor.prototype.getMenuWidth = () => 'narrow';
mw.uls.getFrequentLanguageList = () => [];
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/DiffUndo.js&action=raw&ctype=text/javascript', 's');
pfcm7ayx7g5o6qdo7xdiwg7bcbckn4p
735550
735549
2026-03-29T15:36:56Z
Nardog
40946
735550
javascript
text/javascript
/* globals ve */
// window.dn?.disable?.();
window.diffnowExtraSelector = '.catchangesviewer-table td:nth-child(2) > .mw-changeslist-links > span:first-child > a, .listtools-last > a, .mw-special-AbuseLog form li > a[href^="/wiki/Special:AbuseLog/"], .unseendiff';
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
mw.loader.using(['mediawiki.util', 'user.options'], function diffNow() {
let isHist = mw.config.get('wgAction') === 'history';
let selector = `:where(.mw-changeslist-diff, .mw-changeslist-diff-cur, .mw-changeslist-groupdiff, .mw-fr-reviewlink > a, .mw-fr-hist-difflink a, .mw-history-histlinks a, .mw-logevent-loglines a[href*="&diff="], .mw-fr-pending-changes-table .cdx-docs-link, .mw-special-AbuseLog #mw-content-text > form > ul > li > a:last-of-type[href*="&diff="], .mw-deletedcontribs-tools > a:first-child, .mw-undelete-revlist > li > a:first-of-type[href*="&diff="], #merge li > a:first-of-type[href*="&diff="], .mw-history-compareselectedversions-button, .consecudiff > a${window.diffnowExtraSelector ? ', ' + window.diffnowExtraSelector : ''}):not(.external${window.diffnowNegativeSelector ? ', ' + window.diffnowNegativeSelector : ''})`;
let switchSelector = '.diffnow-differences-prevlink, .diffnow-differences-nextlink, .diffnow-switch';
let diffs = [], count = 0, expanded = new WeakSet();
window.dn = diffs;
let sanitize = href => {
let url = new URL(href, location.href);
let newParams = new URLSearchParams(), hasId;
['diff', 'direction', 'oldid', 'target', 'timestamp'].forEach(k => {
if (url.searchParams.has(k)) {
let v = url.searchParams.get(k);
newParams.set(k, v);
hasId = hasId || v > 0;
}
});
if (!hasId && url.searchParams.has('title')) {
return `${url.search.match(/[&?](title=[^&]*)/)[1]}&${newParams}`;
}
return String(newParams);
};
let getDiff = comp => {
if (typeof comp === 'string') {
let sanitized = sanitize(comp);
return diffs.find(diff => (
diff.queries.has(comp) || diff.queries.has(sanitized)
));
}
return diffs.find(diff => diff.$diff.is(comp));
};
let containers = [];
window.dn._c = containers;
class Diff {
constructor(href, $link) {
this.aborter = new AbortController();
this.queries = new Set();
this.setLink(href, $link);
diffs.push(this);
}
setLink(href, $link) {
if (this.href) {
if ($link.is(this.$link)) {
if (this.aborter) {
this.aborter.abort();
} else if (this.isVisible()) {
this.close();
} else {
this.$anchor = this.getAnchor();
this.append();
}
return;
}
if ($link.is(this.$outerLink) && this.isVisible()) {
this.close();
return;
}
if (this.getAnchor($link).is(this.$anchor)) {
this.markLink(false);
} else {
this.close();
}
}
this.href = href;
this.queries.add(href);
let sanitized = sanitize(href);
if (sanitized) {
this.queries.add(sanitized);
}
this.$link = $link;
this.isSwitch = $link.is(switchSelector);
if (!this.isSwitch) {
this.$outerLink = $link;
}
this.$anchor = this.getAnchor();
if (this.aborter) {
this.markLink();
} else {
this.append();
}
}
append(response) {
if (response) {
delete this.aborter;
this.findDiff(response);
this.setQueries();
this.polishDiff();
}
let $container = this.getContainer(true);
if (!$container[0].isConnected) {
if (this.$anchor.is('tr')) {
let cols = this.$anchor.children().get()
.reduce((acc, cell) => acc + cell.colSpan, 0);
$('<tr>').addClass('diffnow-row').append(
$('<td>').attr('colspan', cols).append($container)
).insertAfter(this.$anchor);
} else if (this.$anchor.is('div')) {
this.$anchor.after($container);
} else {
this.$anchor.append($container);
}
}
this.attached = true;
filterContexts(this.$diff);
window.addEventListener('resize', onResize);
if (!this.$link.hasClass('diffnow-link-loaded')) {
mw.requestIdleCallback(() => {
this.markLinks();
this.markSeen();
});
}
if ($container[0].getBoundingClientRect().top < 0) {
$container[0].scrollIntoView();
}
this.markLink(true);
if (response) {
mw.hook('wikipage.content').fire($container);
}
}
findDiff(response) {
if (typeof response !== 'string') throw '';
let $diff = $($.parseHTML(response))
.filter('.diff[data-mw-interface]');
if (!$diff.length) {
$diff = $diff.end().find('.diff[data-mw-interface]');
if (!$diff.length) throw '';
}
this.$diff = $($diff[0]);
}
setQueries() {
let newPerma = this.$diff[0].querySelector(
'#mw-diff-ntitle1 > strong > a, #differences-nextlink'
);
if (newPerma) {
this.newId = parseInt(mw.util.getParamValue('oldid', newPerma.search));
this.newTitle = newPerma.title;
}
let oldPerma = this.$diff[0].querySelector(
'#mw-diff-otitle1 > strong > a, #differences-prevlink'
);
if (oldPerma) {
this.oldId = parseInt(mw.util.getParamValue('oldid', oldPerma.search));
this.oldTitle = oldPerma.title;
}
let isSamePage = !oldPerma || this.newTitle === this.oldTitle;
this.isSingle = isSamePage && !this.$diff[0].querySelector('.diff-multi');
if (this.newId) {
if (this.newId === this.oldId) {
throw 'nonfatal';
}
if (this.oldId) {
this.queries.add(`diff=${this.newId}&oldid=${this.oldId}`);
}
this.isLast = !this.$diff[0].querySelector('#differences-nextlink');
if (this.isSingle) {
this.queries.add(`diff=prev&oldid=${this.newId}`);
if (this.isLast && this.oldId) {
this.queries.add(`diff=0&oldid=${this.oldId}`);
this.queries.add(`diff=cur&oldid=${this.oldId}`);
}
}
}
if (this.oldId && this.isSingle) {
this.queries.add(`diff=next&oldid=${this.oldId}`);
}
this.queries.forEach(q => {
let other = getDiff(q);
if (other && other !== this) {
other.setLink(this.href, this.$link);
throw 'nonfatal';
}
});
if (this.oldId) {
diffs.forEach(diff => {
if (diff.isLast && diff.newId === this.oldId) {
diff.markNotLast(this);
}
});
}
}
async polishDiff() {
mw.hook('wikipage.diff').fire(this.$diff);
let prevLink = this.$diff[0].querySelector('#differences-prevlink');
let nextLink = this.$diff[0].querySelector('#differences-nextlink');
$([prevLink, nextLink].filter(Boolean)).attr('href', (_, href) => (
href.replace('&diffonly=1', '').replace('&expand-url=1', '')
));
[['previousdiff', prevLink], ['nextdiff', nextLink]].forEach(([key, link]) => {
if (link && !mw.messages.exists(key)) {
mw.messages.set(key, link.textContent);
}
});
if (!nextLink && this.isLast) {
this.$diff.find('#mw-diff-ntitle4').empty().append(
$('<a>').attr({
class: 'diffnow-checknext diffnow-switch',
href: mw.util.getUrl(this.newTitle, {
diff: 'next',
oldid: this.newId
}),
title: this.newTitle
}).text('Check for newer edit')
);
}
this.$diff.find('.mw-diff-movedpara-left, .mw-diff-movedpara-right')
.attr('href', (_, href) => `#diffnow-${count}-${href.slice(1)}`);
this.$diff.find('a[name^="movedpara"]')
.attr('name', (_, name) => `diffnow-${count}-${name}`);
count++;
this.$diff.find('[id]').addClass(function () {
return 'diffnow-' + this.id;
}).removeAttr('id');
if (this.$diff[0].querySelector('.mw-thanks-thank-link')) {
mw.loader.load('ext.thanks.corethank');
mw.config.set('thanks-confirmation-required', true);
}
if (!this.isSingle) {
let keys = ['nextdiff', 'previousdiff']
.filter(s => !mw.messages.exists(s));
if (keys.length) {
await mw.loader.using('mediawiki.api');
await new mw.Api().loadMessagesIfMissing(keys);
}
this.$diff.find('.diff-multi').append(
$('<div>').append(
$('<div>').append(
$('<a>').attr({
class: 'diffnow-multinext diffnow-switch',
href: mw.util.getUrl(this.oldTitle, {
diff: 'next',
oldid: this.oldId
}),
title: this.oldTitle
}).text(mw.msg('nextdiff'))
),
$('<div>').append(
$('<a>').attr({
class: 'diffnow-multiprev diffnow-switch',
href: mw.util.getUrl(this.newTitle, {
diff: 'prev',
oldid: this.newId
}),
title: this.newTitle
}).text(mw.msg('previousdiff'))
)
)
);
}
}
getAnchor($link) {
let isSwitch;
if ($link) {
isSwitch = $link.is(switchSelector);
} else {
$link = this.$link;
isSwitch = this.isSwitch;
}
if (isSwitch) {
return getDiff($link.closest('.diff')).$anchor;
}
return $link.closest('li, tr, .mw-history-compareselectedversions');
}
getContainer(create) {
let $container = this.$anchor.is('tr')
? this.$anchor.next('.diffnow-row').find('> td > .diffnow')
: this.$anchor.is('div')
? this.$anchor.next('.diffnow')
: this.$anchor.children('.diffnow');
if (create) {
if ($container.length) {
this.attachDiff($container);
} else {
$container = this.createContainter();
}
}
return $container;
}
attachDiff($container) {
let $oldDiff = $container.children('.diff');
if (!$oldDiff.is(this.$diff)) {
if ($oldDiff.length) {
getDiff($oldDiff).detachDiff();
}
$container.children('.diffnow-tools-top').after(this.$diff);
}
$container.find('.diffnow-difflink').attr('href', this.href);
}
createContainter() {
let $container = containers.pop();
if ($container) {
this.attachDiff($container);
return $container;
}
return $('<div>').addClass('diffnow').append(
$('<div>').addClass('diffnow-tools diffnow-tools-top').append(
$('<button>').attr({
class: 'diffnow-button diffnow-close oo-ui-icon-collapse',
title: 'Hide this diff'
})
),
this.$diff,
$('<div>').addClass('diffnow-tools diffnow-tools-bottom').append(
$('<button>').attr({
class: 'diffnow-button diffnow-scrollup oo-ui-icon-collapse',
title: 'Scroll to top'
}),
$('<button>').attr({
class: 'diffnow-button diffnow-scrolldown oo-ui-icon-expand',
title: 'Scroll to bottom'
}),
$('<button>').attr({
class: 'diffnow-button diffnow-close oo-ui-icon-close',
title: 'Hide this diff'
}),
$('<a>').attr({
class: 'diffnow-button diffnow-difflink oo-ui-icon-newWindow oo-ui-image-progressive',
href: this.href,
target: '_blank',
title: 'Open diff page'
})
)
)
.on('click', '.diffnow-close', this.collapse)
.on('click', '.diffnow-scrollup', this.scrollUp)
.on('click', '.diffnow-scrolldown', this.scrollDown)
.on(
'click keydown',
'.diffnow-context-collapsed > td, .diffnow-context-expanded > .diff-marker',
this.toggleContext
);
}
isVisible() {
return !!this.$diff?.[0].offsetParent;
}
markLink(visible = this.isVisible()) {
if (this.isSwitch) {
this.getContainer().toggleClass('diffnow-switching', !!this.aborter);
if (!this.$outerLink) return;
}
this.$outerLink
.toggleClass('diffnow-link-loading', !!this.aborter)
.toggleClass('diffnow-link-loaded', !this.aborter)
.toggleClass('diffnow-link-open', !this.isSwitch && visible);
}
markLinks() {
$(selector).filter((_, link) => {
let href = link.getAttribute('href');
return this.queries.has(href) || this.queries.has(sanitize(href));
}).addClass('diffnow-link-loaded');
if (!isHist) return;
$('.mw-history-compareselectedversions-button').toggleClass(
'diffnow-link-loaded',
!!getDiff(getHistHref())
);
}
markSeen() {
if (this.$link.is('.mw-rcfilters-ui-highlights-enhanced-nested:nth-child(n+2) .mw-changeslist-diff')) {
this.$anchor.nextAll().addBack()
.removeClass('mw-changeslist-watchedunseen mw-changeslist-line-watched mw-enhanced-watched')
.addClass('mw-changeslist-watchedseen mw-changeslist-line-not-watched');
return;
}
let $li = this.$anchor.closest('li, table');
let $unseen = $li.filter('.mw-changeslist-watchedunseen')
.add($li.find('.mw-changeslist-watchedunseen'));
if (!$unseen.length) return;
$unseen.removeClass('mw-changeslist-watchedunseen')
.addClass('mw-changeslist-watchedseen');
$li.filter('.mw-changeslist-line-watched')
.add($li.find('.mw-changeslist-line-watched'))
.removeClass('mw-changeslist-line-watched mw-enhanced-watched')
.addClass('mw-changeslist-line-not-watched');
}
async markNotLast(newDiff) {
this.isLast = false;
this.queries = new Set([...this.queries].filter(q => (
!/^title=|(?:^|[&?])(?:diff|oldid)=(?:0|cur)(?:&|$)/.test(q)
)));
this.$diff.find(
'.diffnow-mw-diff-ntitle1 a, .diffnow-mw-diff-ntitle1 .history-deleted'
).first().text(
newDiff.$diff.find(
'#mw-diff-otitle1 a, #mw-diff-otitle1 .history-deleted'
).first().text()
);
this.$diff.find('.diff-ntitle .mw-diff-edit a')
.attr('href', (_, href) => href + '&oldid=' + this.newId);
if (!mw.messages.exists('nextdiff')) {
await mw.loader.using('mediawiki.api');
await new mw.Api().loadMessagesIfMissing(['nextdiff']);
}
this.$diff.find('.diffnow-checknext')
.attr('class', 'diffnow-differences-nextlink')
.text(mw.msg('nextdiff'));
}
detachDiff() {
this.$diff?.detach();
this.markLink(false);
}
close(adjustFocus) {
this.detachDiff();
let $container = this.getContainer();
if ($container.length) {
let $row = $container.closest('.diffnow-row');
containers.push($container.detach());
$row.remove();
}
if (!adjustFocus) return;
setTimeout(() => {
this.$anchor.find('a[href]').last().each(function () {
this.focus();
this.blur();
});
});
}
collapse(e) {
e.preventDefault();
let $container = $(e.delegateTarget);
let diff = getDiff($container.children('.diff'));
if (diff.$link[0].getBoundingClientRect().top < 0) {
diff.$anchor[0].scrollIntoView();
$container.fadeOut('fast', () => {
diff.close(true);
setTimeout(() => {
diff.$anchor[0].scrollIntoView();
$container.removeAttr('style');
});
});
} else {
diff.close(true);
}
}
scrollUp(e) {
e.preventDefault();
let y = e.delegateTarget.getBoundingClientRect().top;
let ch = document.documentElement.clientHeight, buffer = ch / 2;
y -= y + 5 > buffer ? ch : buffer;
window.scrollBy({ top: y, behavior: 'smooth' });
}
scrollDown(e) {
e.preventDefault();
let y = e.delegateTarget.getBoundingClientRect().bottom;
let buffer = document.documentElement.clientHeight / 2;
if (y - 5 > buffer) {
y -= buffer;
}
window.scrollBy({ top: y, behavior: 'smooth' });
}
toggleContext(e) {
if (e.type === 'keydown' && (
e.which !== 13 && e.which !== 32 ||
e.ctrlKey || e.shiftKey || e.metaKey || e.altKey
)) {
return;
}
e.preventDefault();
let $row = $(e.target.closest('tr'));
if ($row.hasClass('diffnow-context-expanded')) {
$row.removeClass('diffnow-context-expanded')
.addClass('diffnow-context-collapsed')
.children().attr({
tabindex: 0,
role: 'button',
title: 'Expand'
});
expanded.delete($row[0]);
} else {
$row.removeClass('diffnow-context-collapsed')
.addClass('diffnow-context-expanded')
.children('.diff-context').removeAttr('tabindex role title')
.siblings('.diff-marker').attr('title', 'Collapse');
expanded.add($row[0]);
}
}
destroy() {
diffs.splice(diffs.indexOf(this), 1);
this.$link.removeClass('diffnow-link-loading diffnow-link-loaded diffnow-link-open');
this.getContainer().removeClass('diffnow-switching');
}
}
let filterContexts = $diff => {
$diff.find('.diff-context.diff-side-deleted > div').each(function () {
let $row = $(this.closest('tr'))
.removeClass('diffnow-context-expanded');
if (this.scrollHeight > this.clientHeight) {
if (expanded.has($row[0])) {
$row.addClass('diffnow-context-expanded')
.children('.diff-marker').attr({
tabindex: 0,
role: 'button',
title: 'Collapse'
});
} else {
$row.addClass('diffnow-context-collapsed').children().attr({
tabindex: 0,
role: 'button',
title: 'Expand'
});
}
} else {
$row.removeClass('diffnow-context-collapsed')
.children().removeAttr('tabindex role title');
}
});
};
let onResize = mw.util.debounce(() => {
filterContexts($('.diffnow > .diff'));
}, 250);
let notif;
let showError = async (href, msg) => {
notif = await mw.notify([
document.createTextNode(msg || `Couldn't load the diff`),
$('<p>').append(
$('<a>').attr({
href: href,
target: '_blank'
}).text('Open it in a new tab')
)[0]
], { autoHideSeconds: 'long', tag: 'diffnow', type: 'error' });
};
let getHistHref = () => {
let formData = new FormData(document.getElementById('mw-history-compare'));
return mw.util.getUrl(null, {
diff: formData.get('diff'),
oldid: formData.get('oldid')
});
};
$(document.body).on('click.diffnow', 'a, .mw-history-compareselectedversions-button', async function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
!this.matches(selector) && !this.matches(switchSelector)
) {
return;
}
e.preventDefault();
let $link = $(this);
let isCompare = $link.hasClass('mw-history-compareselectedversions-button');
let href = isCompare ? getHistHref() : this.pathname + this.search;
let diff = getDiff(href);
try {
if (diff) {
diff.setLink(href, $link);
return;
}
if (!isCompare && this.origin !== location.origin) {
throw '';
}
diff = new Diff(href, $link);
let url = new URL(href, location.href);
url.searchParams.set('diffonly', 1);
url.searchParams.set('action', 'render');
let promise = fetch(url, { signal: diff.aborter.signal });
mw.loader.using(['mediawiki.diff', 'mediawiki.diff.styles']);
if (notif) {
notif.close();
notif = null;
}
diff.append(await (await promise).text());
} catch (error) {
if (diff && !diff.attached) {
diff.destroy();
}
if (error.name === 'AbortError') {
notif = await mw.notify('Diff loading canceled', { tag: 'diffnow' });
} else if (error !== 'nonfatal') {
showError(href, error);
console.error(error);
}
}
});
let css = mw.loader.addStyleTag(`${selector} {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand.svg);
background-position: right;
background-repeat: no-repeat;
background-size: 10px 10px;
padding-right: 12px;
}
.mw-history-compareselectedversions-button {
background-position: right 6px center;
background-size: 14px 14px;
padding-right: 24px !important;
}
.skin-timeless .mw-history-compareselectedversions-button {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand-invert.svg);
}
.skin-timeless .mw-history-compareselectedversions-button.diffnow-link-loaded {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/reload-invert.svg) !important;
}
.skin-timeless .mw-history-compareselectedversions-button.diffnow-link-open {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/collapse-invert.svg) !important;
}
.diffnow-link-loading {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cstyle type='text/css'%3Ecircle%7Banimation:bounce-delay 600ms infinite ease-in-out both;animation-delay:-80ms;transform-origin:center;transform-box:fill-box%7Dcircle:first-of-type%7Banimation-delay:-160ms%7Dcircle:last-of-type%7Banimation-delay:0ms%7D@keyframes bounce-delay%7B0%25,50%25,100%25%7Btransform:scale(0)%7D20%25%7Bopacity:0.87;transform:scale(1)%7D%7D%3C/style%3E%3Ccircle cx='2.5' cy='10' r='2.5'/%3E%3Ccircle cx='10' cy='10' r='2.5'/%3E%3Ccircle cx='17.5' cy='10' r='2.5'/%3E%3C/svg%3E") !important;
}
.diffnow-link-loaded {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/reload.svg) !important;
}
.diffnow-link-open, .diffnow-context-expanded > .diff-marker {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/collapse.svg) !important;
}
.diffnow {
background: var(--background-color-base, #fff);
border: var(--border-base, 1px solid #a2a9b1);
border-radius: 4px;
position: relative;
}
.mw-history-compareselectedversions + .diffnow {
margin-top: 0.3em;
}
.diffnow-tools {
display: flex;
position: sticky;
background-color: var(--background-color-backdrop-light, rgba(255,255,255,0.65));
z-index: 1;
}
.diffnow-tools-top {
top: 0;
border-radius: 4px;
}
.diffnow-tools-bottom {
bottom: 0;
}
.diffnow-tools > .diffnow-button {
cursor: pointer;
height: 24px;
padding: 0;
box-sizing: content-box;
background-position: center;
background-repeat: no-repeat;
background-size: 16px 16px;
background-color: var(--background-color-transparent, transparent);
flex-grow: 1;
}
.diffnow-tools > .diffnow-button:hover {
background-color: var(--background-color-button-quiet--hover, rgba(0,24,73,0.027));
}
.diffnow-tools > .diffnow-button:active {
background-color: var(--background-color-button-quiet--active, rgba(0,24,73,0.082));
}
.diffnow-tools-top > .diffnow-button {
border: none;
border-radius: 3px 3px 0 0;
}
.diffnow-tools-bottom > .diffnow-button {
border: var(--border-base, 1px solid #a2a9b1);
border-radius: 99px;
margin: 4px;
height: 20px;
}
.diffnow-switching > .diff {
transition: opacity 200ms;
opacity: 0.4;
}
.diffnow-checknext, .diffnow-checknext:visited {
color: var(--color-subtle, #54595d);
}
.diffnow :not(.diffnow-context-expanded) > .diff-context > div {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.diffnow-context-collapsed > td,
.diffnow-context-expanded > .diff-marker {
cursor: pointer !important;
background-position: center;
background-repeat: no-repeat;
background-size: 12px 12px;
}
.diffnow-context-collapsed > .diff-marker {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand.svg);
}
.diffnow-context-collapsed > .diff-context a {
pointer-events: none;
}
.diffnow .diff-multi > div {
display: flex;
justify-content: space-around;
}
.diffnow > .diff td div {
word-break: break-word;
}
.diffnow .mw-diff-inline-changed ins,
.diffnow .mw-diff-inline-changed del {
white-space: pre-wrap;
}
.diffnow-row .diffnow {
border: none;
}
.diffnow-row > td {
padding: 0 !important;
}
td.mw-changeslist-line-inner {
width: 100%;
}
.mw-enhanced-rc .diffnow > .diff td {
padding: 0.33em 0.5em;
}
.cdx-table__table-wrapper:has(.diffnow-row) {
overflow-x: visible;
}
span.mw-history-histlinks-current,
span.mw-history-histlinks-previous {
padding-right: 12px;
}
.client-js .diffnow .mw-anonuserlink {
padding-right: 0;
}`);
mw.loader.using(['oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-interactions']);
diffs._disable = () => {
$(document.body).off('.diffnow');
css.remove();
};
if (isHist) {
$(document.body).on('change.diffnow', '#mw-history-compare', function () {
let $buttons = $('.mw-history-compareselectedversions-button');
let diff = getDiff(getHistHref());
if (diff) {
$buttons.addClass('diffnow-link-loaded');
if ($buttons.is(diff.$link) && diff.isVisible()) {
diff.$link.addClass('diffnow-link-open');
} else {
$buttons.removeClass('diffnow-link-open');
}
} else {
$buttons.removeClass('diffnow-link-loaded diffnow-link-open');
}
});
}
let added, tempLoaded, ipInfoLoaded;
mw.hook('wikipage.content').add(async () => {
await new Promise(mw.requestIdleCallback);
if (!document.querySelector(selector)) return;
let modules = ['mediawiki.diff', 'mediawiki.diff.styles'];
let cspn = mw.config.get('wgCanonicalSpecialPageName');
let loadTemp = !tempLoaded &&
Number(mw.user.options.get('checkuser-temporary-account-enable')) &&
!['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(cspn);
if (loadTemp) {
modules.push('ext.checkUser.tempAccounts', 'ext.checkUser.styles');
}
if (Number(mw.user.options.get('checkuser-userinfocard-enable'))) {
modules.push('ext.checkUser.userInfoCard');
}
let loadIpInfo;
if (Number(mw.user.options.get('ipinfo-beta-feature-enable'))) {
modules.push('ext.ipInfo', 'ext.ipInfo.styles');
loadIpInfo = !ipInfoLoaded &&
['Contributions', 'DeletedContributions', 'IPContributions'].includes(cspn);
}
let promise = mw.loader.using(modules);
if (loadTemp || loadIpInfo) {
tempLoaded = loadTemp;
ipInfoLoaded = loadIpInfo;
await promise;
// https://gerrit.wikimedia.org/g/mediawiki/core/+/master/resources/src/startup/mediawiki.loader.js
let makeRequire = (moduleObj, basePath) => moduleName => {
let relParts = moduleName.match(/^((?:\.\.?\/)+)(.*)$/);
if (!relParts) {
return mw.loader.require(moduleName);
}
let baseDirParts = basePath.split('/');
baseDirParts.pop();
let prefixes = relParts[1].split('/');
prefixes.pop();
let prefix;
while ((prefix = prefixes.pop())) {
if (prefix === '..') {
baseDirParts.pop();
}
}
let fileName = baseDirParts.join('/');
if (fileName) {
fileName += '/';
}
fileName += relParts[2];
if (Object.hasOwn(moduleObj.packageExports, fileName)) {
return moduleObj.packageExports[fileName];
}
let scriptFiles = moduleObj.script.files;
if (!Object.hasOwn(scriptFiles, fileName)) {
throw Error('Cannot require undefined file ' + fileName);
}
let result;
let fileContent = scriptFiles[fileName];
if (typeof fileContent === 'function') {
let moduleParam = { exports: {} };
fileContent(makeRequire(moduleObj, fileName), moduleParam, moduleParam.exports);
result = moduleParam.exports;
} else {
result = fileContent;
}
moduleObj.packageExports[fileName] = result;
return result;
};
if (loadTemp) {
try {
let pack = mw.loader.moduleRegistry['ext.checkUser.tempAccounts'];
makeRequire(pack, pack.script.main)('./initOnHook.js')();
} catch {}
}
if (loadIpInfo) {
try {
let pack = mw.loader.moduleRegistry['ext.ipInfo'];
makeRequire(pack, pack.script.main)('./popup/init.js')();
} catch {}
}
}
if (added || !document.getElementById('p-cactions')) return;
added = true;
let callback = (records, observer) => {
if (records[0].target.classList.contains('diffnow-link-loaded')) {
observer.takeRecords();
observer.disconnect();
clickFirst();
}
};
let clickFirst = () => {
let link = document.querySelector(selector);
if (!link || link.matches('.diffnow-link-loaded, [href*="&diff=cur&"], .consecudiff > a')) {
return;
}
new MutationObserver(callback).observe(link, { attributeFilter: ['class'] });
link.click();
};
mw.util.addPortletLink('p-cactions', '#', 'Expand all diffs').firstElementChild.addEventListener('click', e => {
e.preventDefault();
clickFirst();
});
});
});
// window.smpg?._disable?.();
mw.loader.using('mediawiki.util', async function smoothPager() {
let action = mw.config.get('wgAction');
let isHist = action === 'history';
let isPerma = action === 'view' &&
['oldid', 'diff'].some(s => mw.util.getParamValue(s));
let ns = !isHist && !isPerma && mw.config.get('wgNamespaceNumber');
let isFile = ns === 6;
let isCat = ns === 14;
let cspn = ns === -1 && mw.config.get('wgCanonicalSpecialPageName');
if (!(isHist || isPerma || isFile || isCat || cspn)) return;
let isSearch = cspn === 'Search';
let isWl = cspn === 'Watchlist';
let isRc = cspn === 'Recentchanges' || cspn === 'Recentchangeslinked';
if ((isWl || isRc) && document.body.classList.contains('mw-rcfilters-enabled')) {
return;
}
let isContribs = cspn === 'Contributions' || cspn === 'IPContributions';
let o = {
useRender: isCat || isWl || isRc,
selector: isPerma ? '#bodyContent' :
isFile ? '#mw-imagepage-section-filehistory' :
isCat ? '.mw-category-generated' :
'#mw-content-text',
outerSelectors: [],
linkSelector: isPerma ? '#differences-prevlink, #differences-nextlink, #mw-diff-ntitle1 > strong > a, #mw-diff-otitle1 > strong > a, #mw-revision-nav > a, .fr-diff-to-stable > a' :
isCat ? '#mw-subcategories > a, #mw-pages > a, #mw-category-media > a' :
isWl ? '#ca-nstab-special > a, .mw-watchlist-toollink-active > a, #p-associated-pages .selected > a' :
isRc ? '#ca-nstab-special > a, .rclinks > a, .rcshowhideoption > a, .rclistfrom > a' :
`#ca-nstab-special > a, .mw-pager-navigation-bar > a, .TablePager_nav > .oo-ui-widget-enabled > a, .mw-datatable th > a, .cdx-table-pager .cdx-button--fake-button--enabled, .cdx-table__table__cell--has-sort > a, .mw-prefixindex-nav > a, .mw-allpages-nav > a, .CategoryTreeParents .CategoryTreeLabel, .mw-special-Newpages form div > a, .mw-abusefilter-history-buttons a${isHist ? ', #ca-history > a' : isSearch ? ', .search-types a, .searchdidyoumean a' : ''}`,
canPost: isSearch || [
'AbuseFilter', 'ExpandTemplates', 'TemplateSandbox'
].includes(cspn),
paramRe: isPerma && /^(?:diff|diffonly|direction|oldid)$/ ||
isSearch && /^(?:advancedSearch-current|limit|offset|profile|runsuggestion|search|sort|ns\d+)$/,
reruns: [
'mediawiki.action.history',
'mediawiki.special.search',
'ext.advancedSearch.init',
'mediawiki.special.watchlist',
'mediawiki.special.watchlistedit',
'mediawiki.pager.codex',
'mediawiki.misc-authed-curate',
'mediawiki.misc-authed-ooui',
'mediawiki.misc-authed-pref',
'mediawiki.special.unwatchedPages',
'ext.thanks.corethank',
'ext.flaggedRevs.review',
'ext.gadget.watchlist-notice-core'
],
pages: []
};
if (isContribs) {
o.reruns.push('ext.ipInfo');
}
if (!o.useRender) {
o.outerSelectors.push('#firstHeading', '.mw-indicators');
if (!isPerma && !isFile) {
o.outerSelectors.push('#mw-content-subtitle');
}
}
o.formSelector = isHist && '#mw-history-searchform' ||
isSearch && '#search, #powersearch, #searchform' ||
cspn && `#mw-content-text form${o.canPost ? '': ':not([method="post"])'}`;
if (o.formSelector) {
o.formSelector += ', .cdx-table-pager__limit-form';
}
window.smpg = o;
let getHref = () => location.pathname + location.search;
let getKey = (href, isPost) => {
if (o.paramRe) {
let params = new URLSearchParams();
new URL(href, location.href).searchParams.forEach((v, k) => {
if (o.paramRe.test(k)) {
params.set(k, v);
}
});
params.sort();
return String(params);
}
if (isPost) {
return href + '??' + Date.now();
}
return href;
};
class Page {
constructor(href, key) {
this.href = href || getHref();
this.key = key || getKey(this.href);
this.keys = new Set().add(this.key);
o.pages.push(this);
}
init($response) {
if ($response) {
if (isWl || isRc) {
this.$cont = $(document.querySelector(o.selector).cloneNode())
.removeClass('smoothpager-switching')
.append($response);
} else {
let $cont = $response.filter(o.selector).not('script');
if (!$cont.length) {
$cont = $response.find(o.selector).not('script');
if (!$cont.length) {
throw 'Content not found';
}
}
$cont.find('script').remove();
this.$cont = $($cont[0]);
}
this.attached = 0;
} else {
if (this.$cont) return;
this.$cont = $(document.querySelector(o.selector));
this.attached = 1;
}
this.getConfigAndModules($response);
this.getOuterEls($response);
this.getDocTitle($response);
this.getRev($response);
}
getConfigAndModules($response) {
if (o.useRender) return;
if (!$response) {
this.config = Object.assign({}, window.RLCONF);
}
let $scripts = $response
? $response.filter('script').remove()
: $('head > script, body > script');
$scripts.map(function () {
return this.textContent.match(/\bmw\.config\.set\({.+}\);/g);
}).each((_, s) => {
let obj;
do {
try {
obj = JSON.parse(s.slice(14, -2));
} catch {
let i = s.slice(0, -3).lastIndexOf('});');
if (i === -1) return;
s = s.slice(0, i + 3);
}
} while (!obj);
this.config = Object.assign(this.config || {}, obj);
});
if (!$response) return;
let match = $scripts.first().text().replaceAll('\n', '')
.match(/;RLCONF=({.+});RLSTATE=({.+});RLPAGEMODULES=(\[.+\]);$/);
if (match) {
try {
this.config = Object.assign(this.config || {}, JSON.parse(match[1]));
} catch {}
this.modules = [];
try {
this.modules.push(...Object.keys(JSON.parse(match[2])));
} catch {}
try {
this.modules.push(...JSON.parse(match[3]));
} catch {}
}
this.redirect();
}
setConfig() {
if (!this.config) return;
mw.config.set(this.config);
if (!o.cur.config) return;
Object.entries(o.cur.config).forEach(([k, v]) => {
if (!Object.hasOwn(this.config, k) &&
JSON.stringify(mw.config.get(k)) === JSON.stringify(v)
) {
delete mw.config.values[k];
}
});
}
getOuterEls($response) {
this.outerEls = o.outerSelectors.map(selector => (
$response ? $response.find(selector)[0] : document.querySelector(selector)
)).map(el => el && !this.$cont[0].contains(el) && el);
}
setOuterEls() {
o.outerSelectors.forEach((selector, i) => {
let outer = document.querySelector(selector);
if (!outer) return;
$(outer).before(this.outerEls[i] || outer.cloneNode()).detach();
});
}
getDocTitle($response) {
if (o.useRender) return;
if ($response) {
let $title = $response.filter('title');
if ($title.length) {
this.docTitle = $title.text();
}
} else {
this.docTitle = document.title;
}
}
setDocTitle() {
if (this.docTitle) {
document.title = this.docTitle;
} else if (this.outerEls[0] && o.cur.outerEls?.[0]) {
document.title = document.title.replace(
o.cur.outerEls[0].textContent.trim(),
this.outerEls[0].textContent.trim()
);
}
}
getRev($response) {
if (!isPerma) return;
let params = new URLSearchParams(this.key);
if (params.has('diff')) {
if ($response) {
let dir = params.get('diff') === 'next' ? 'prev' : 'next';
let id = this.config?.[
dir === 'prev' ? 'wgDiffNewId' : 'wgDiffOldId'
];
if (!id) {
id = this.findParam(
'oldid',
dir === 'prev'
? '#mw-diff-ntitle1 > strong > a, #differences-prevlink'
: '#mw-diff-otitle1 > strong > a, #differences-nextlink'
);
}
if (id) {
this.rev = dir === 'prev' ? id : params.get('oldid');
params.set('diff', dir);
params.set('oldid', id);
this.keys.add(String(params));
}
} else {
this.rev = mw.config.get('wgDiffNewId');
let pn = this.findParam(
'title',
'#mw-diff-ntitle1 > strong > a, #differences-nextlink'
);
if (pn === mw.config.get('wgPageName') &&
!this.$cont[0].querySelector('.diff[data-mw-interface] .diff-multi')
) {
params.set('diff', 'prev');
params.set('oldid', this.rev);
this.keys.add(String(params));
let oldId = mw.config.get('wgDiffOldId');
if (oldId) {
params.set('diff', 'next');
params.set('oldid', oldId);
this.keys.add(String(params));
}
}
}
} else {
if ($response) {
if (params.has('direction')) {
let id = this.config?.wgRevisionId;
if (!id) {
id = this.findParam(
'oldid',
'#t-permalink > a, #p-tb .menu__item--page-actions-overflow-permalink',
$response
);
}
if (id) {
this.rev = id;
this.keys.add('oldid=' + id);
o.cur.keys.add(`direction=${
params.get('direction') === 'next' ? 'prev' : 'next'
}&oldid=${id}`);
}
} else {
this.rev = params.get('oldid');
}
} else {
this.rev = mw.config.get('wgRevisionId');
if (params.has('direction')) {
this.keys.add('oldid=' + this.rev);
}
}
}
}
findParam(param, query, $range = this.$cont) {
let search = $range.find(query).prop('search');
return search && mw.util.getParamValue(param, search);
}
updateLinks() {
if (this.rev) {
$('#ca-edit > a').attr('href', mw.util.getUrl(null, {
action: 'edit',
oldid: this.rev
}));
$('#t-permalink > a, #p-tb .menu__item--page-actions-overflow-permalink')
.attr('href', mw.util.getUrl(null, { oldid: this.rev }));
}
$('#ca-nstab-special > a').attr('href', this.href);
$('.printfooter > a').attr('href', location.href).text(location.href);
$('#footer-places-mobileview > a, #mw-mf-display-toggle').attr('href', function () {
let url = new URL(location.href);
url.searchParams.set(
'mobileaction',
mw.util.getParamValue('mobileaction', this.search)
);
return '//' + this.host + url.pathname + url.search;
});
$('#t-urlshortener > a').attr('href', function () {
let params = new URLSearchParams(this.search);
params.set('url', location.href);
return this.pathname + '?' + params;
});
}
redirect() {
if (this.config.wgAction !== mw.config.get('wgAction') ||
this.config.wgPageName?.replace(/\/.*/, '') !== mw.config.get('wgPageName').replace(/\/.*/, '')
) {
location.href = this.href;
throw 'redirect';
}
}
replaceContent(isPopState, isRefresh) {
let oldCont = document.querySelector(o.selector);
if (this.$cont.is(oldCont)) {
throw 'Attempt to replace content with itself';
}
if (!isPopState && !isRefresh) {
this.pushState();
}
$(oldCont).before(this.$cont).detach();
console.log(this.key, o);
this.setOuterEls();
this.setDocTitle();
this.setConfig();
if (isSearch && this.attached) {
let input = this.$cont[0].querySelector('#searchText > input');
if (input) {
input.value = input.defaultValue;
}
}
o.cur = this;
this.loadModules(isPopState);
this.updateLinks();
}
pushState() {
history.pushState({ _smpg: this.key }, '', this.href);
history.replaceState({ _smpg: this.key }, '', this.href);
}
async loadModules(isPopState) {
o.pending = true;
try {
await mw.loader.using(this.modules || []);
} catch {} finally {
o.pending = false;
if (!this.attached) {
this.fireHooks();
this.rerunModules();
}
if (!isPopState) {
this.scroll();
}
this.attached++;
}
}
fireHooks() {
mw.hook('wikipage.content').fire(this.$cont);
this.$cont.find('.diff[data-mw-interface]').each(function () {
mw.hook('wikipage.diff').fire($(this));
});
this.$cont.find('.catlinks[data-mw-interface]').each(function () {
mw.hook('wikipage.categories').fire($(this));
});
mw.hook('htmlform.enhance').fire(this.$cont);
}
rerunModules() {
o.reruns.forEach(m => {
let pack = mw.loader.moduleRegistry[m];
if (!pack || pack.state !== 'ready') return;
if (typeof pack.script === 'function') {
pack.script($, $, mw.loader.require, pack.module);
return;
}
delete mw.loader.moduleRegistry[m];
mw.loader.implement(
m + '@' + pack.version, pack.script, pack.style,
pack.messages, pack.templates, pack.deprecationWarning
);
});
if ((isHist || isContribs || isRc) && window.Twinkle) {
window.Twinkle.rollback();
}
}
scroll() {
let form = o.formSelector && this.$cont[0].querySelector(o.formSelector);
if (form) {
let y = form.getBoundingClientRect().bottom;
if (y < 0) {
window.scrollBy(0, y);
}
} else if (this.$cont[0].getBoundingClientRect().top < 0) {
this.$cont[0].scrollIntoView();
}
}
}
o.cur = new Page();
let getContent = async (href, state, options) => {
if (o.pending) return;
o.aborter?.abort();
let key = state?._smpg || getKey(href, !!options);
let isRefresh = key === o.cur.key;
if (state && isRefresh) return;
let oldCont = document.querySelector(o.selector);
if (!oldCont) {
notify(href, 'No element to replace', 'error');
return;
}
let page = o.pages.find(p => p.keys.has(key));
if ((state || !isRefresh) && page) {
page.href = href;
page.replaceContent(!!state, isRefresh);
return;
}
oldCont.classList.add('smoothpager-switching');
let $throbber = $('<div>').addClass('smoothpager-throbber')
.appendTo(document.body);
if (!isRefresh && o.pages.length === 1) {
o.cur.init();
}
o.aborter = new AbortController();
let url = href;
if (o.useRender) {
url = new URL(href, location.href);
url.searchParams.set('action', 'render');
}
let promise = fetch(url, Object.assign({
signal: o.aborter.signal
}, options));
if (notif) {
notif.close();
notif = null;
}
try {
let response = await (await promise).text();
if (page) {
page.href = href;
} else {
page = new Page(href, key);
}
page.init($($.parseHTML(response, !o.useRender)));
page.replaceContent(!!state, isRefresh);
} catch (e) {
if (e.name === 'AbortError') {
if (state && o.cur) {
o.cur.pushState();
}
} else if (e === 'redirect') {
notify(href, 'Redirecting...');
} else {
notify(href, e || `Couldn't load the page`, 'error');
console.error(e);
}
} finally {
o.aborter = null;
oldCont.classList.remove('smoothpager-switching');
$throbber.remove();
}
};
let notif;
let notify = async (href, msg, type) => {
notif = await mw.notify([
document.createTextNode(msg),
$('<p>').append(
$('<a>').attr({
href: href,
target: '_blank'
}).text('Open it in a new tab')
)[0]
], {
autoHideSeconds: 'long',
tag: 'smoothpager',
type: type
});
};
let onClick = function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
!this.matches(o.linkSelector) || this.origin !== location.origin
) {
return;
}
e.preventDefault();
getContent(this.pathname + this.search);
};
let onKeyDown = e => {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
e.target.closest(':read-write')
) {
return;
}
switch (e.which) {
case 27:
o.aborter?.abort();
break;
case 110:
case 190:
e.preventDefault();
getContent(getHref());
}
};
let onPopState = e => {
let state = e.originalEvent.state;
if (state && !state._smpg) return;
history.replaceState(state, '', location.href);
getContent(getHref(), state || {});
};
let onSubmit = e => {
if (e.originalEvent && (
e.originalEvent.defaultPrevented || !e.originalEvent.isTrusted
) || !o.canPost && e.target.method !== 'get' ||
!e.target.matches(o.formSelector)
) {
return;
}
let path = e.target.getAttribute('action');
let isScript = path === mw.config.get('wgScript');
let comps = [mw.config.get('wgPageName')];
if (mw.config.get('wgNamespaceNumber') === -1 && comps[0].includes('/')) {
comps.push(comps[0].replace(/\/.*/, ''));
}
comps.push((comps[1] || comps[0]) + '/');
if (!isScript && !comps.some(c => mw.util.getUrl(c) === path)) return;
let formData = new FormData(e.target);
if (isScript && !comps.includes(formData.get('title').replaceAll(' ', '_'))) {
return;
}
e.preventDefault();
e.stopPropagation();
if (isSearch && e.target.id !== 'searchform') {
formData.set('search', OO.ui.infuse($('#searchText')).getValue());
} else if (e.target.method === 'post') {
let submitter = e.originalEvent?.submitter;
if (submitter?.name) {
formData.append(submitter.name, submitter.value);
}
getContent(path, null, {
method: 'POST',
headers: { 'Content-Type': e.target.enctype },
body: e.target.enctype === 'multipart/form-data'
? formData
: new URLSearchParams(formData)
});
return;
}
getContent(path + '?' + new URLSearchParams(formData));
};
let setPortlet = text => {
if (!o.portletLink) return;
$(o.portletLink).find('*').addBack().contents().each(function () {
if (this.nodeType === 3 && this.textContent.trim()) {
this.textContent = text;
return false;
}
});
};
o._enable = () => {
o.enabled = true;
let $body = $(document.body).on('click', 'a', onClick);
if (o.formSelector) {
$body.on('submit', onSubmit);
}
$body.parent().on('keydown', onKeyDown);
$(window).on('popstate', onPopState);
if (o.css) {
o.css.disabled = false;
}
if (isPerma) {
mw.trackSubscribe('counter.MediaWiki.RevisionSlider.event.init', o._disable);
}
setPortlet('Disable SmoothPager');
};
o._disable = () => {
o.enabled = false;
$(document.body).off('click', onClick).off('submit', onSubmit)
.parent().off('keydown', onKeyDown);
$(window).off('popstate', onPopState);
o.css.disabled = true;
mw.trackUnsubscribe(o._disable);
setPortlet('Enable SmoothPager');
};
o._enable();
o.css = mw.loader.addStyleTag(`${o.linkSelector},
.cdx-table__table__sort-button {
color: #008064 !important;
}
:is(${o.linkSelector}, .cdx-table__table__sort-button):visited {
color: #006400 !important;
}
:is(${o.linkSelector}, .cdx-table__table__sort-button):active {
color: #faa700 !important;
}
.cdx-table-pager .cdx-button--fake-button--enabled > .cdx-button__icon {
background-color: #14866d;
}
.smoothpager-switching {
opacity: 0.75;
}
.smoothpager-throbber {
width: 20%;
height: 0.5vh;
position: fixed;
top: 0;
left: 0;
background-color: var(--background-color-progressive, #36c);
transform: translate(-100%);
animation: smoothpager-throbber 1s infinite linear;
}
@keyframes smoothpager-throbber {
to {
transform: translate(700%);
}
}
@media (prefers-reduced-motion: reduce) {
.smoothpager-throbber {
animation: smoothpager-throbber 1s infinite steps(10,end) !important;
width: 40%;
}
}${o.formSelector ? `
:is(${o.formSelector}) input[type="submit"],
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
color: var(--color-inverted, #fff);
background-color: #14866d;
border-color: #14866d;
}
:is(${o.formSelector}) input[type="submit"]:hover,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover {
background-color: #00af89;
border-color: #00af89;
}
:is(${o.formSelector}) input[type="submit"]:focus,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
border-color: #14866d;
}
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
box-shadow: inset 0 0 0 1px #14866d, inset 0 0 0 2px #fff;
}
:is(${o.formSelector}) input[type="submit"]:active,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active {
background-color: #0e725a;
border-color: #0e725a;
box-shadow: none;
}` : ''}`);
await $.ready;
if (!document.getElementById('p-cactions')) return;
o.portletLink = mw.util.addPortletLink('p-cactions', '#', 'Disable SmoothPager').firstElementChild;
o.portletLink.addEventListener('click', e => {
e.preventDefault();
o[o.enabled ? '_disable' : '_enable']();
});
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], function smartDiff() {
mw.loader.addStyleTag('.smartdiff-link.extiw, .smartdiff-link.external{color:var(--color-progressive,#36c)} .smartdiff-link.extiw:visited, .smartdiff-link.external:visited{color:#795cb2} .smartdiff-link.extiw:active, .smartdiff-link.external:active{color:#faa700}');
class SmartDiff {
constructor($diff) {
this.$diff = $diff;
this.isSpecial = mw.config.get('wgNamespaceNumber') === -1;
this.isView = mw.config.get('wgAction') === 'view' &&
new URLSearchParams(location.search).get('diffonly') !== '1';
this.magicWords = [
'!', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME',
'CURRENTDOW', 'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1',
'CURRENTMONTHABBREV', 'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN',
'CURRENTTIME', 'CURRENTTIMESTAMP', 'CURRENTVERSION', 'CURRENTWEEK',
'CURRENTYEAR', 'DEFAULTCATEGORYSORT:', 'DEFAULTSORT:', 'DEFAULTSORTKEY:',
'DISPLAYTITLE:', 'filepath:', 'formatnum:', 'FULLPAGENAME',
'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'int:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2',
'LOCALDAYNAME', 'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1',
'LOCALMONTHABBREV', 'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME',
'LOCALTIMESTAMP', 'LOCALWEEK', 'LOCALYEAR', 'msg:', 'msgnw:',
'NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:', 'NAMESPACENUMBER',
'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS', 'NUMBEROFARTICLES',
'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES', 'NUMBEROFUSERS',
'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:', 'PAGESINCATEGORY:',
'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2', 'REVISIONDAY2:',
'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:',
'TALKSPACEE', 'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
if (window.smartdiffMagicWords) {
this.magicWords.push(...window.smartdiffMagicWords);
}
try {
this.subNs = mw.config.get('wgVisualEditorConfig').namespacesWithSubpages;
} catch (e) {}
if (!this.subNs) {
this.subNs = Object.keys(mw.config.get('wgFormattedNamespaces'))
.map(k => Number(k)).filter(ns => ![0, 6, 8].includes(ns));
}
this.re = /((?:\[(?:<[^>]*>)?\[|(?<!{(?:<[^>]*>)?){(?:<[^>]*>)?{(?:<[^>]*>)?(?:(?:#(?:<[^>]*>)?invoke|(?:safe)?subst|msg(?:nw)?|raw|int)(?:<[^>]*>)?:)?)(?:\s*(?:<[^>]*>)?<(?:<[^>]*>)?tvar(?:<[^>]*>)?\s(?!>).*?>)?\s*)((?:(?!&[gl]t;)[^\[\]{|}])+?)(?=\s*(?:(?:<[^>]*>)?<(?:<[^>]*>)?\/(?:<[^>]*>)?tvar(?:<[^>]*>)?>(?:<[^>]*>)?\s*)?(?:\||\](?:<[^>]*>)?\]|}(?:<[^>]*>)?}|$))/g;
this.headRe = /^((?:(?:<[^>]*>)*=){1,6}(?:<[^>]*>)?\s*)((?:(?!&[gl]t;).)+?)(?=\s*(?:(?:<[^>]*>)?=){1,6}(?:<[^>]*>|\s)*(?:<|$))/g;
this.galleryRe = /^(\s*)((?:(?!&[gl]t;)[^\[\]{|}])+\.(?:<[^>]*>)?(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))(?=\s*(?:(?:<[^>]*>)?(?:<[^>]*>)?\s*)?(?:\||$))/gi;
this.urlRe = /(\[(?:<[^>]*>)?(?=.+\]))?((\bhttps?(?:<[^>]*>)?:)?(?:<[^>]*>)?\/(?:<[^>]*>)?\/(?:<[^>]*>|(?!&[gl]t;)[^\s"<>\[\]{|}])+)/g;
if (window.smartdiffTemplates) {
this.tempRe = /( data-smartdiff-temp="(\d+)">[^{|}]+)(\|(?:(?!&[gl]t;)[^\[\]{}]|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?})+)(?=}(?:<[^>]*>)?}|$)/g;
this.tempSubRe = /((?:\s|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?}[^<>|]*|<[^>]*>)*(?:\|(?:\s|(?:<[^>]*>)|\d+(?:\s|<[^>]*>)*=|[^\d<=>|](?:[^<=>|]|<[^>]*>)*=(?:[^<=>|]|<[^>]*>)*\|?)*|$))/;
this.templates = window.smartdiffTemplates;
}
this.side = 'old';
$diff.find('.diff-deletedline > div').get().forEach(this.processDiv);
this.side = 'new';
$diff.find('.diff-addedline > div').get().forEach(this.processDiv);
let $contexts = $diff.find('.diff-context > div');
$contexts.each((i, div) => {
if (i % 2) {
this.side = 'new';
if (this.propUsed && this.getProp() !== this.getProp('pn', 'old')) {
this.processDiv(div);
} else {
$contexts.eq(i).replaceWith($contexts.eq(i - 1).clone());
}
} else {
this.side = 'old';
this.propUsed = false;
this.processDiv(div);
}
});
this.links = {};
$diff.find('.smartdiff-link:not(.external)').each((i, link) => {
let title = link.title;
if (!title) return;
if (!this.links.hasOwnProperty(title)) {
this.links[title] = [];
}
this.links[title].push(link);
});
this.query(Object.keys(this.links).slice(0, 500));
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
}
processDiv = div => {
if (div.querySelector('a[href]')) return;
let origHtml = div.innerHTML;
let newHtml = origHtml.replace(this.urlRe, this.urlRep)
.replace(this.galleryRe, this.galleryRep)
.replace(this.re, this.rep).replace(this.headRe, this.headRep);
if (this.tempRe) {
newHtml = newHtml.replace(this.tempRe, this.tempRep);
}
if (newHtml === origHtml) return;
newHtml = newHtml.replace(/<(ins|del)(?: [^>]+)?><\/\1>/g, '');
let $newDiv = $('<div>').html(newHtml);
if (this.detectErrors($newDiv, newHtml, origHtml, div)) return;
div.textContent = '';
$newDiv.contents().appendTo(div);
};
rep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
let t = mw.Title.newFromText(s), isTemp;
if (t) {
if ($1.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s[0] === '/') {
if (this.subNs.includes(this.getProp('ns'))) {
t = mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
} else if ($1[0] === '{') {
t.namespace = 10;
}
} else if ($1[0] === '{') {
if (s[0] === '#') {
return $0;
}
if ($1.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (!t.namespace && s[0] !== ':') {
if (!$1.includes('msg') && !$1.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && this.magicWords.includes(match[0])) {
return $0;
}
}
t.namespace = 10;
isTemp = true;
}
} else if ((this.isSpecial || !this.isView) && s[0] === '#') {
t.title = this.getProp();
}
} else if (s.startsWith('../') && this.subNs.includes(this.getProp('ns'))) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
t = mw.Title.newFromText(sub ? sup + '/' + sub : sup);
}
}
if (!t) {
return $0;
}
let attrs = {
class: 'smartdiff-link',
href: t.getUrl()
};
if (this.isSpecial || !this.isView || s[0] !== '#') {
attrs.title = t.toText();
}
if (isTemp && this.tempRe) {
let name = t.getMainText();
let idx = this.templates.findIndex(temp => temp.names.includes(name));
if (idx !== -1) {
attrs['data-smartdiff-temp'] = idx;
}
}
return pre + $('<a>').attr(attrs).html(mid)[0].outerHTML + post;
};
stripTags(s, decode, pre = '', post = '') {
let mid = s, tags = s.match(/<\/?(?:ins|del)(?: [^>]+)?>/g);
s = $($.parseHTML(s.replace(/&/g, '&'))).text();
if (decode) {
try {
s = decodeURIComponent(s);
} catch (e) {}
}
if (tags) {
if (tags[0][1] === '/') {
pre += tags[0];
mid = `<${tags[0].slice(2, 5)} class="diffchange diffchange-inline">` + mid;
}
let lastTag = tags.pop();
if (lastTag[1] !== '/') {
mid += `</${lastTag.slice(1, 4)}>`;
post = lastTag + post;
}
}
return [s, pre, mid, post];
}
headRep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
s = s.replace(/'''(.+?)'''|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>/gi, '$1')
.replace(/''(.+?)''/g, '$1')
.replace(/^_+|_+$/g, '');
let t = mw.Title.newFromText(
`${this.isSpecial || !this.isView ? this.getProp() : ''}#${s}`
);
if (!t) {
return $0;
}
let attrs = {
class: 'smartdiff-link',
href: t.getUrl()
};
if (this.isSpecial || !this.isView) {
attrs.title = t.toText();
}
return pre + $('<a>').attr(attrs).html(mid)[0].outerHTML + post;
};
galleryRep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
let t = mw.Title.newFromText(s, 6);
if (!t) {
return $0;
}
return pre + $('<a>').attr({
class: 'smartdiff-link',
href: t.getUrl(),
title: t.toText()
}).html(mid)[0].outerHTML + post;
};
urlRep = ($0, $1, $2, $3) => {
let main = $2, trail;
if (!$1) {
if (!$3) {
return $0;
}
let re = main.includes('(')
? /[!,.:;?](?:<[^>]*>)?$/
: /[!),.:;?](?:<[^>]*>)?$/;
let match = main.match(re);
if (match &&
!/&(?:;(?:<[^>]*>)?(?:[a-z]+|(?:#(?:<[^>]*>)?(?:x[\da-f]|\d+)))(?:<[^>]*>)?)?;$/i.test(main)
) {
trail = match[0];
main = main.slice(0, -trail.length);
}
}
let [url, pre, mid, post] = this.stripTags(main);
if ($1) {
pre = $1 + pre;
} else if (trail) {
post += trail;
}
return pre + $('<a>').attr({
class: 'smartdiff-link external',
href: url,
rel: 'nofollow'
}).html(mid)[0].outerHTML + post;
};
tempRep = ($0, $1, $2, $3) => {
if ($3.includes('<a class="smartdiff-link')) {
return $0;
}
let temp = this.templates[$2];
return $1 + $3.split(this.tempSubRe).map((os, i) => {
if (!os || i % 2) {
return os;
}
let j = i / 2;
if (j < temp.start || j > temp.end ||
temp.skipOdd && j % 2 || temp.skipEven && j % 2 === 0
) {
return os;
}
let [s, pre, mid, post] = this.stripTags(os, true);
if (temp.prefix) {
s = temp.prefix + s;
}
if (temp.suffix) {
s += temp.suffix;
}
let t = temp.forceNs
? mw.Title.makeTitle(temp.namespace, s)
: mw.Title.newFromText(s, temp.namespace);
if (!t) {
return os;
}
let params = (j >= temp.noRedirectStart || j <= temp.noRedirectEnd) &&
{ redirect: 'no' };
return pre + $('<a>').attr({
class: 'smartdiff-link',
href: t.getUrl(params),
title: t.toText()
}).html(mid)[0].outerHTML + post;
}).join('');
};
getProp(n = 'pn', side = this.side) {
this.propUsed = true;
if (this[side]) {
if (this[side][n]) {
return this[side][n];
}
} else {
this[side] = {};
let link = this.$diff[0].querySelector(
side === 'old'
? '#mw-diff-otitle1 a, #differences-prevlink'
: '#mw-diff-ntitle1 a, #differences-nextlink'
);
if (link) {
let pn = mw.util.getParamValue('title', link.search);
this[side].pn = pn;
this[side].ns = mw.Title.newFromText(pn).namespace;
return this[side][n];
}
}
if (this[n]) {
return this[n];
}
if (this.isSpecial) {
this.pn = '';
this.ns = 0;
} else {
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
}
return this[n];
}
query(titles) {
if (!titles.length) return;
new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
}).then(response => {
let query = response && response.query;
if (!query) return;
let data = {};
(query.pages || []).forEach(page => {
let obj = { classes: page.linkclasses || [] };
if (page.missing && !page.known) {
obj.classes.push('new');
obj.params = { action: 'edit', redlink: 1 };
}
data[page.title] = obj;
});
(query.interwiki || []).forEach(interwiki => {
data[interwiki.title] = {
classes: ['extiw'],
url: interwiki.url
};
});
(query.normalized || []).forEach(entry => {
if (!data.hasOwnProperty(entry.to)) return;
let obj = data[entry.to];
obj.canonical = entry.to;
if (!obj.url) {
obj.url = mw.util.getUrl(entry.to, obj.params);
}
data[entry.from] = obj;
});
Object.entries(data).forEach(([title, obj]) => {
if (!this.links.hasOwnProperty(title)) return;
let $links = $(this.links[title]).addClass(obj.classes)
.attr('title', obj.canonical);
if (obj.url) {
$links.attr('href', function () {
return obj.url + this.hash;
});
}
});
this.query(titles.slice(50));
});
}
detectErrors($newDiv, newHtml, origHtml, div) {
let comp = $newDiv.html();
if (comp !== newHtml) {
console.warn(
'SmartDiff syntax error at:\n',
div,
`\nNew HTML:\n${newHtml}\nCompared against:\n${comp}`
);
this.hasError = true;
return true;
}
let $comp = $newDiv.clone();
$comp.find('.smartdiff-link').contents().unwrap();
comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
console.warn(
'SmartDiff mutation error at:\n',
div,
`\nOriginal HTML:\n${origHtml}\nCompared against:\n${comp}`
);
this.hasError = true;
return true;
}
}
}
mw.hook('wikipage.diff').add($diff => {
new SmartDiff($diff);
});
});
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopySectLink.js&action=raw&ctype=text/javascript', 's');
mw.loader.using([
'ext.visualEditor.desktopArticleTarget.init', 'mediawiki.storage'
], function ipaInput() {
if (!mw.libs.ve.isVisualAvailable &&
!['edit', 'submit'].includes(mw.config.get('wgAction'))
) {
return;
}
mw.loader.addStyleTag(`.oo-ui-icon-schwa{background-image:url("data:image/svg+xml,%3Csvg width='20' height='20' version='1.1' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m0 3v14h4v-2h-2v-10h2v-2zm16 0v2h2v10h-2v2h4v-14zm-6 2c-2.41 0-4.43 1.73-4.9 4h2.08c0.41-1.17 1.5-2 2.82-2 1.67 0 3 1.33 3 3h-8c0 2.75 2.25 5 5 5 2.75 0 5-2.25 5-5 0-2.75-2.25-5-5-5zm-2.59 6.5h5.18c-0.516 0.895-1.47 1.5-2.59 1.5-1.12 0-2.07-0.605-2.59-1.5z'/%3E%3C/svg%3E")}`);
let clicked;
let openDialog = () => {
if (clicked) {
if (window.ipaInputDialog) {
window.ipaInputDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox4.js&action=raw&ctype=text/javascript');
mw.loader.using([
'jquery.textSelection', 'oojs-ui-windows', 'oojs-ui-widgets',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-editing-core',
'oojs-ui.styles.icons-editing-advanced'
]);
};
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.wikiEditor('addToToolbar', {
section: 'main',
group: 'insert',
tools: {
ipainput: {
label: 'IPAInput',
type: 'button',
oouiIcon: 'schwa',
action: { type: 'callback', execute: openDialog }
}
}
});
});
mw.hook('ve.loadModules').add(addPlugin => {
addPlugin(() => {
ve.ui.IpaInputCommand = function VeUiIpaInputCommand() {
ve.ui.IpaInputCommand.super.call(this, 'ipaInput');
};
OO.inheritClass(ve.ui.IpaInputCommand, ve.ui.Command);
ve.ui.IpaInputCommand.prototype.execute = () => {
openDialog();
return true;
};
ve.ui.commandRegistry.register(new ve.ui.IpaInputCommand());
ve.ui.IpaInputTool = function VeUiIpaInputTool() {
ve.ui.IpaInputTool.super.apply(this, arguments);
};
OO.inheritClass(ve.ui.IpaInputTool, ve.ui.Tool);
ve.ui.IpaInputTool.static.name = 'ipaInput';
ve.ui.IpaInputTool.static.group = 'insert';
ve.ui.IpaInputTool.static.icon = 'schwa';
ve.ui.IpaInputTool.static.title = 'IPA';
ve.ui.IpaInputTool.static.commandName = 'ipaInput';
ve.ui.toolFactory.register(ve.ui.IpaInputTool);
});
});
mw.requestIdleCallback(() => {
let expiry = mw.storage.get('_EXPIRY_ipainput-cache');
if (!expiry) return;
$.get(
'//en.wikipedia.org/api/rest_v1/page/title/Module%3AIPA%2Fdata'
).then(response => {
if (Date.parse(response.items[0].timestamp) / 1000 > expiry - 604800) {
mw.storage.remove('ipainput-cache');
}
});
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function wiktIpa() {
mw.loader.addStyleTag(`.oo-ui-icon-wiktionary{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath d='M14.95 1c-.15 0-.3 0-.45.03L2.9 2.9a2.26 2.26 0 0 0-1.87 2.6L2.9 17.1a2.26 2.26 0 0 0 2.6 1.86l11.6-1.88a2.26 2.26 0 0 0 1.86-2.6L17.1 2.9A2.27 2.27 0 0 0 14.95 1zm-.03.9c.63.03 1.17.49 1.28 1.14l1.88 11.6c.12.75-.37 1.43-1.12 1.56l-11.6 1.88a1.34 1.34 0 0 1-1.56-1.12L1.92 5.36A1.34 1.34 0 0 1 3.04 3.8l11.6-1.88.28-.02zm.7 2.61-2.83.46.07.39c.6-.09.95-.14 1.08.36.1.6-.91 6.53-.91 6.53s-2.87-5.16-2.98-5.87c-.02-.34.02-.64.86-.7l-.06-.4-3.64.6.07.38c.5-.15 1.01.02 1.43.82l.7 1.33-.72 4.54s-2.93-5.3-3.03-5.9c-.07-.5.45-.64.8-.66l-.06-.38-3.46.56.06.39c.24-.1.84-.07 1.07.32.07.09 4.54 8.44 4.54 8.44l.33-.05 1.02-6.24 2.98 5.59.36-.06s1.42-9.14 1.48-9.3c.05-.26.28-.71.9-.76l-.07-.39z'/%3E%3C/svg%3E")}`);
let clicked, dialog, input, $result;
let openDialog = async context => {
if (clicked) {
if (dialog) {
let selection = context.$textarea.textSelection('getSelection');
if (selection) {
input.setValue(selection);
}
if ($result) {
$result.prev().addBack().remove();
$result = null;
}
dialog.open({
actions: [{ label: 'Close', flags: ['safe', 'close'] }]
});
}
return;
}
clicked = true;
await mw.loader.using([
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.ForeignApi',
'mediawiki.util'
]);
let response = await new mw.ForeignApi('//en.wiktionary.org/w/api.php').get({
action: 'query',
generator: 'search',
gsrsearch: 'deepcat:Pronunciation_templates_by_language',
gsrnamespace: 10,
gsrlimit: 'max',
gsrsort: 'none',
formatversion: 2
});
let winMan = new OO.ui.WindowManager();
dialog = new OO.ui.MessageDialog();
winMan.addWindows([dialog]);
let items = response.query.pages
.map(p => p.title.slice(9))
.sort(Intl.Collator('en-u-kn-true').compare)
.map(s => new OO.ui.MenuOptionWidget({ label: s }));
let dropdown = new OO.ui.DropdownWidget({
$overlay: dialog.$overlay,
menu: { items }
});
let $doc = $('<p>');
dropdown.getMenu().on('choose', mw.util.debounce(async item => {
let title = 'Template:' + item.getLabel();
let $link = $('<a>').attr({
href: '//en.wiktionary.org/wiki/' + title,
target: '_blank',
title: title
}).text('documentation');
$doc.empty().append('Loading ', $link, '...');
try {
let data = await $.get(
'//en.wiktionary.org/api/rest_v1/page/html/' +
encodeURIComponent(title + '/documentation')
);
let text = $($.parseHTML(data)).find('p').first().text()
.replace(/\. .*/, '.');
$doc.text(text + ' (').append($link.text('read more'), ')');
dialog.updateSize();
} catch {
$doc.empty().append('Failed to load ', $link);
}
}, 100)).selectItem(items[0]);
input = new OO.ui.TextInputWidget({
autocomplete: false,
value: context.$textarea.textSelection('getSelection') ||
mw.config.get('wgTitle')
});
let button = new OO.ui.ButtonWidget({
disabled: !input.getValue(),
label: 'Get',
flags: ['primary', 'progressive']
}).on('click', async () => {
button.setDisabled(true);
let template = dropdown.getMenu().findSelectedItem().getLabel();
let text = input.getValue();
try {
let data = await $.post('//en.wiktionary.org/api/rest_v1/transform/wikitext/to/html', {
wikitext: `{{${template}|1=${text}}}`,
body_only: true
});
if ($result) {
$result.children().remove();
} else {
$result = $('<div>').text('Result:')
.insertAfter(fieldset.$element)
.before('<hr>');
}
$result.append($.parseHTML(data))
.find('.mw-collapsible').makeCollapsible().end()
.find('[id], [about]').removeAttr('id about').end()
.find('a').attr('target', '_blank')
.filter('[href^="./"]').attr('href', (_, href) => (
'//en.wiktionary.org/wiki' + href.slice(1)
));
dialog.updateSize();
} catch {} finally {
button.setDisabled();
}
});
input.on('change', value => {
button.setDisabled(!value);
}).connect(button, { enter: ['emit', 'click'] });
let fieldset = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(dropdown, {
label: 'Template:',
align: 'top'
}),
new OO.ui.FieldLayout(input, {
label: 'Input:',
align: 'top'
}),
new OO.ui.FieldLayout(button)
]
});
dropdown.$element.after($doc);
dialog.text.$element.append(fieldset.$element/*.on('keydown', e => {
e.stopPropagation();
})*/);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
dialog.open({
actions: [{ label: 'Close', flags: ['safe', 'close'] }]
});
};
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.wikiEditor('addToToolbar', {
section: 'main',
group: 'insert',
tools: {
wiktipa: {
label: 'WiktIPA',
type: 'button',
oouiIcon: 'wiktionary',
action: { type: 'callback', execute: openDialog }
}
}
});
});
}());
window.scripttesterSkipWarning = true;
mw.loader.using(['mediawiki.util', 'mediawiki.storage'], async function scriptTester() {
let get = () => new Set(mw.storage.getObject('scripttester'));
if (mw.storage.get('scripttester')) {
let unloading;
window.addEventListener('beforeunload', () => {
unloading = true;
});
get().forEach(async s => {
let isCss = /\.css$/i.test(s);
let url = /^(https?:)?\/\/./.test(s) ? s : mw.util.getUrl(s, {
action: 'raw',
ctype: isCss ? 'text/css' : 'text/javascript'
});
if (isCss) {
mw.loader.load(url, 'text/css');
return;
}
try {
await mw.loader.getScript(url);
} catch (e) {
if (unloading) {
console.warn(e);
return;
}
mw.notify('Failed to load temporarily installed ' + s, { type: 'error' });
}
});
} else if (!window.scripttesterSkipWarning) {
await mw.loader.using('oojs-ui-windows');
if (await OO.ui.confirm(
'You take full responsibility for any consequences arising from using ScriptTester.'
)) {
mw.storage.setObject('scripttester', []);
}
}
await $.ready;
if (!document.getElementById('p-tb')) return;
let css = mw.loader.addStyleTag('.scripttester-dialog .oo-ui-checkboxMultiselectWidget{margin:0.5em 0;word-break:break-all} .scripttester-dialog .oo-ui-flaggedElement-destructive{float:right}');
let linksShown = mw.config.get('wgNamespaceNumber') > 0 &&
mw.config.get('wgAction') === 'view';
let updateLinks = (s, unins) => {
if (!linksShown) return;
$(`.scripttester-link[data-scripttester="${s}"]`)
.toggleClass('scripttester-installed', !unins);
};
let dialog, multiselect, addButton, removeButton, clearButton;
let openDialog = () => {
if (!dialog) {
dialog = new OO.ui.MessageDialog({ classes: ['scripttester-dialog'] });
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
multiselect = new OO.ui.CheckboxMultiselectWidget().on('select', () => {
removeButton.setDisabled(!multiselect.findSelectedItems().length);
});
addButton = new OO.ui.ButtonWidget({
label: 'Add'
}).on('click', async () => {
dialog.toggle(false);
let s = (await OO.ui.prompt('Add a script', {
textInput: { placeholder: 'Script page name or URL' }
})).trim();
if (!s) return;
if (!/^(https?:)?\/\/./.test(s) && !mw.Title.newFromText(s)) {
await OO.ui.alert(`"${s}" does not appear to be a valid page name or URL.`);
dialog.toggle(true);
updateDialog();
return;
}
mw.storage.setObject('scripttester', [...get().add(s)]);
updateLinks(s);
});
removeButton = new OO.ui.ButtonWidget({
label: 'Remove'
}).on('click', () => {
let set = get();
multiselect.findSelectedItems().forEach(item => {
let s = item.getLabel();
set.delete(s);
updateLinks(s, true);
});
mw.storage.setObject('scripttester', [...set]);
updateDialog();
});
clearButton = new OO.ui.ButtonWidget({
label: 'Clear',
flags: 'destructive'
}).on('click', async () => {
dialog.toggle(false);
if (!(await OO.ui.confirm('Uninstall all scripts?'))) {
dialog.toggle(true);
return;
}
mw.storage.setObject('scripttester', []);
if (linksShown) {
$('.scripttester-installed').removeClass('scripttester-installed');
}
dialog.toggle(true);
updateDialog();
});
dialog.text.$element.append(
multiselect.$element,
new OO.ui.ButtonGroupWidget({
items: [addButton, removeButton]
}).$element,
clearButton.$element
);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
}
updateDialog();
dialog.open({
message: 'Temporarily installed scripts:',
actions: [{ label: 'Done', flags: ['safe', 'close'] }]
});
};
let updateDialog = () => {
let set = get();
multiselect.clearItems().addItems(
[...set].map(s => new OO.ui.CheckboxMultioptionWidget({ label: s }))
);
removeButton.setDisabled(true);
clearButton.toggle(set.size);
dialog.updateSize();
updatePortlet(set.size);
};
let updatePortlet = count => {
$(portletLink).find('*').addBack().contents().each(function () {
if (this.nodeType === 3 && this.textContent.trim()) {
this.textContent = `Temporarily installed scripts (${count})`;
return false;
}
});
};
let portletLink = mw.util.addPortletLink('p-tb', '#', `Temporarily installed scripts (${get().size})`)
.firstElementChild;
portletLink.addEventListener('click', e => {
e.preventDefault();
mw.loader.using(['oojs-ui-windows', 'mediawiki.Title'], openDialog);
});
window.addEventListener('storage', e => {
if (e.key === 'scripttester') {
updatePortlet(get().size);
}
});
if (!linksShown) return;
css.textContent += ' .scripttester{font-size:85%;user-select:none} .scripttester::before{content:" "} .scripttester-link::after{content:"[+]"} .scripttester-installed::after{content:"[−]"} #firstHeading > .scripttester{font-size:47%}';
let linkHandler = function () {
let s = this.dataset.scripttester;
let unins = this.classList.contains('scripttester-installed');
let set = get();
set[unins ? 'delete' : 'add'](s);
let success = mw.storage.setObject('scripttester', [...set]);
if (success) {
mw.notify((unins ? 'Uninstalled ' : 'Installed ') + s, {
tag: 'scripttester'
});
updateLinks(s, unins);
updatePortlet(set.size);
} else {
mw.notify(`Couldn't ${unins ? 'un' : ''}install ${s}`, {
tag: 'scripttester',
type: 'error'
});
}
};
if ([2, 4, 8].includes(mw.config.get('wgNamespaceNumber')) &&
['javascript', 'css'].includes(mw.config.get('wgPageContentModel'))
) {
let s = mw.config.get('wgPageName').replaceAll('_', ' ');
$('<span>').addClass('scripttester').append(
$('<a>').attr({
class: 'scripttester-link' + (get().has(s) ? ' scripttester-installed' : ''),
tabindex: 0,
role: 'button',
'data-scripttester': s
}).text('\u200e').on('click', linkHandler)
).appendTo(document.getElementById('firstHeading'));
return;
}
mw.hook('wikipage.content').add($content => {
let set = get();
let ns = mw.config.get('wgFormattedNamespaces');
let re = new RegExp(`^(${ns[2]}|${ns[4]}|${ns[8]}):.+\\.([Cc][Ss]|[Jj])[Ss]$`);
$content.find('a:not(.external, .new)').after(function () {
let s = this.title;
if (!s || !re.test(s)) return;
return $('<span>').addClass('scripttester').append(
$('<a>').attr({
class: 'scripttester-link' + (set.has(s) ? ' scripttester-installed' : ''),
tabindex: 0,
role: 'button',
'data-scripttester': s
}).text('\u200e').on('click', linkHandler)
);
});
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.on('keydown', async e => {
if (e.which !== 72 || e.shiftKey || e.altKey || [e.ctrlKey, e.metaKey].filter(Boolean).length !== 1) return;
e.preventDefault();
let context = $textarea.data('wikiEditorContext');
context.api.openDialog(context, 'search-and-replace');
await mw.loader.using('jquery.textSelection');
let tb = document.getElementById('wikieditor-toolbar-replace-search');
let sel = $textarea.textSelection('getSelection');
if (sel) {
tb.value = sel;
}
tb.focus();
});
$(document.body).on('dialogclose', '#wikieditor-toolbar-replace-dialog', () => {
$textarea[0].focus();
});
});
mw.config.get('wgNamespaceNumber') &&
mw.config.get('wgAction') !== 'history' &&
(function catChangeHighlighter() {
let run;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-changeslist-line.mw-changeslist-src-mw-categorize').each(function () {
let text = this.querySelector('.comment').textContent;
if (text.includes(' added ')) {
this.classList.add('catchangehighlighter-addition');
} else if (text.includes(' removed ')) {
this.classList.add('catchangehighlighter-removal');
}
if (run) return;
run = true;
mw.loader.addStyleTag('.catchangehighlighter-addition :is(span, td) > .comment{background:#f5fff5} .catchangehighlighter-removal :is(span, td) > .comment{background:#fff5f5}');
});
});
}());
(mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') !== 'view' ||
[-1, 14].includes(mw.config.get('wgNamespaceNumber'))) &&
(function diffFontSwitcher() {
mw.loader.addStyleTag('.diff-lineno{cursor:pointer}');
$(document.body).on('click keydown', '.diff-lineno', function (e) {
if (e.type === 'keydown' && (
e.which !== 13 && e.which !== 32 ||
e.ctrlKey || e.shiftKey || e.metaKey || e.altKey
)) {
return;
}
e.preventDefault();
this.closest('.diff').classList.toggle('difffontswitcher-enabled');
});
mw.hook('wikipage.diff').add($diff => {
$diff.find('.diff-lineno').attr({ tabindex: 0, role: 'button' });
});
}());
mw.trackSubscribe('resourceloader.exception', (topic, data) => {
mw.notify(data.exception, {
autoHide: false,
title: `Exception in ${data.source} in module ${data.module}`,
type: 'warn'
});
});
mw.config.get('skin') === 'vector-2022' &&
$(document).one('click', '.mw-interlanguage-selector', async () => {
await mw.loader.using('ext.uls.mediawiki');
$.fn.uls.Constructor.prototype.getMenuWidth = () => 'narrow';
mw.uls.getFrequentLanguageList = () => [];
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/DiffUndo.js&action=raw&ctype=text/javascript', 's');
grpuwifpsq94fh5c1u7krejga4hw1yd
735551
735550
2026-03-29T15:38:42Z
Nardog
40946
735551
javascript
text/javascript
/* globals ve */
// window.dn?.disable?.();
window.diffnowExtraSelector = '.catchangesviewer-table td:nth-child(2) > .mw-changeslist-links > span:first-child > a, .listtools-last > a, .mw-special-AbuseLog form li > a[href^="/wiki/Special:AbuseLog/"], .unseendiff';
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
mw.loader.using(['mediawiki.util', 'user.options'], function diffNow() {
let isHist = mw.config.get('wgAction') === 'history';
let selector = `:where(.mw-changeslist-diff, .mw-changeslist-diff-cur, .mw-changeslist-groupdiff, .mw-fr-reviewlink > a, .mw-fr-hist-difflink a, .mw-history-histlinks a, .mw-logevent-loglines a[href*="&diff="], .mw-fr-pending-changes-table .cdx-docs-link, .mw-special-AbuseLog #mw-content-text > form > ul > li > a:last-of-type[href*="&diff="], .mw-deletedcontribs-tools > a:first-child, .mw-undelete-revlist > li > a:first-of-type[href*="&diff="], #merge li > a:first-of-type[href*="&diff="], .mw-history-compareselectedversions-button, .consecudiff > a${window.diffnowExtraSelector ? ', ' + window.diffnowExtraSelector : ''}):not(.external${window.diffnowNegativeSelector ? ', ' + window.diffnowNegativeSelector : ''})`;
let switchSelector = '.diffnow-differences-prevlink, .diffnow-differences-nextlink, .diffnow-switch';
let diffs = [], count = 0, expanded = new WeakSet();
window.dn = diffs;
let sanitize = href => {
let url = new URL(href, location.href);
let newParams = new URLSearchParams(), hasId;
['diff', 'direction', 'oldid', 'target', 'timestamp'].forEach(k => {
if (url.searchParams.has(k)) {
let v = url.searchParams.get(k);
newParams.set(k, v);
hasId = hasId || v > 0;
}
});
if (!hasId && url.searchParams.has('title')) {
return `${url.search.match(/[&?](title=[^&]*)/)[1]}&${newParams}`;
}
return String(newParams);
};
let getDiff = comp => {
if (typeof comp === 'string') {
let sanitized = sanitize(comp);
return diffs.find(diff => (
diff.queries.has(comp) || diff.queries.has(sanitized)
));
}
return diffs.find(diff => diff.$diff.is(comp));
};
let containers = [];
window.dn._c = containers;
class Diff {
constructor(href, $link) {
this.aborter = new AbortController();
this.queries = new Set();
this.setLink(href, $link);
diffs.push(this);
}
setLink(href, $link) {
if (this.href) {
if ($link.is(this.$link)) {
if (this.aborter) {
this.aborter.abort();
} else if (this.isVisible()) {
this.close();
} else {
this.$anchor = this.getAnchor();
this.append();
}
return;
}
if ($link.is(this.$outerLink) && this.isVisible()) {
this.close();
return;
}
if (this.getAnchor($link).is(this.$anchor)) {
this.markLink(false);
} else {
this.close();
}
}
this.href = href;
this.queries.add(href);
let sanitized = sanitize(href);
if (sanitized) {
this.queries.add(sanitized);
}
this.$link = $link;
this.isSwitch = $link.is(switchSelector);
if (!this.isSwitch) {
this.$outerLink = $link;
}
this.$anchor = this.getAnchor();
if (this.aborter) {
this.markLink();
} else {
this.append();
}
}
append(response) {
if (response) {
delete this.aborter;
this.findDiff(response);
this.setQueries();
this.polishDiff();
}
let $container = this.getContainer(true);
if (!$container[0].isConnected) {
if (this.$anchor.is('tr')) {
let cols = this.$anchor.children().get()
.reduce((acc, cell) => acc + cell.colSpan, 0);
$('<tr>').addClass('diffnow-row').append(
$('<td>').attr('colspan', cols).append($container)
).insertAfter(this.$anchor);
} else if (this.$anchor.is('div')) {
this.$anchor.after($container);
} else {
this.$anchor.append($container);
}
}
this.attached = true;
filterContexts(this.$diff);
window.addEventListener('resize', onResize);
if (!this.$link.hasClass('diffnow-link-loaded')) {
mw.requestIdleCallback(() => {
this.markLinks();
this.markSeen();
});
}
if ($container[0].getBoundingClientRect().top < 0) {
$container[0].scrollIntoView();
}
this.markLink(true);
if (response) {
mw.hook('wikipage.content').fire($container);
}
}
findDiff(response) {
if (typeof response !== 'string') throw '';
let $diff = $($.parseHTML(response))
.filter('.diff[data-mw-interface]');
if (!$diff.length) {
$diff = $diff.end().find('.diff[data-mw-interface]');
if (!$diff.length) throw '';
}
this.$diff = $($diff[0]);
}
setQueries() {
let newPerma = this.$diff[0].querySelector(
'#mw-diff-ntitle1 > strong > a, #differences-nextlink'
);
if (newPerma) {
this.newId = parseInt(mw.util.getParamValue('oldid', newPerma.search));
this.newTitle = newPerma.title;
}
let oldPerma = this.$diff[0].querySelector(
'#mw-diff-otitle1 > strong > a, #differences-prevlink'
);
if (oldPerma) {
this.oldId = parseInt(mw.util.getParamValue('oldid', oldPerma.search));
this.oldTitle = oldPerma.title;
}
let isSamePage = !oldPerma || this.newTitle === this.oldTitle;
this.isSingle = isSamePage && !this.$diff[0].querySelector('.diff-multi');
if (this.newId) {
if (this.newId === this.oldId) {
throw 'nonfatal';
}
if (this.oldId) {
this.queries.add(`diff=${this.newId}&oldid=${this.oldId}`);
}
this.isLast = !this.$diff[0].querySelector('#differences-nextlink');
if (this.isSingle) {
this.queries.add(`diff=prev&oldid=${this.newId}`);
if (this.isLast && this.oldId) {
this.queries.add(`diff=0&oldid=${this.oldId}`);
this.queries.add(`diff=cur&oldid=${this.oldId}`);
}
}
}
if (this.oldId && this.isSingle) {
this.queries.add(`diff=next&oldid=${this.oldId}`);
}
this.queries.forEach(q => {
let other = getDiff(q);
if (other && other !== this) {
other.setLink(this.href, this.$link);
throw 'nonfatal';
}
});
if (this.oldId) {
diffs.forEach(diff => {
if (diff.isLast && diff.newId === this.oldId) {
diff.markNotLast(this);
}
});
}
}
async polishDiff() {
mw.hook('wikipage.diff').fire(this.$diff);
let prevLink = this.$diff[0].querySelector('#differences-prevlink');
let nextLink = this.$diff[0].querySelector('#differences-nextlink');
$([prevLink, nextLink].filter(Boolean)).attr('href', (_, href) => (
href.replace('&diffonly=1', '').replace('&expand-url=1', '')
));
[['previousdiff', prevLink], ['nextdiff', nextLink]].forEach(([key, link]) => {
if (link && !mw.messages.exists(key)) {
mw.messages.set(key, link.textContent);
}
});
if (!nextLink && this.isLast) {
this.$diff.find('#mw-diff-ntitle4').empty().append(
$('<a>').attr({
class: 'diffnow-checknext diffnow-switch',
href: mw.util.getUrl(this.newTitle, {
diff: 'next',
oldid: this.newId
}),
title: this.newTitle
}).text('Check for newer edit')
);
}
this.$diff.find('.mw-diff-movedpara-left, .mw-diff-movedpara-right')
.attr('href', (_, href) => `#diffnow-${count}-${href.slice(1)}`);
this.$diff.find('a[name^="movedpara"]')
.attr('name', (_, name) => `diffnow-${count}-${name}`);
count++;
this.$diff.find('[id]').addClass(function () {
return 'diffnow-' + this.id;
}).removeAttr('id');
if (this.$diff[0].querySelector('.mw-thanks-thank-link')) {
mw.loader.load('ext.thanks.corethank');
mw.config.set('thanks-confirmation-required', true);
}
if (!this.isSingle) {
let keys = ['nextdiff', 'previousdiff']
.filter(s => !mw.messages.exists(s));
if (keys.length) {
await mw.loader.using('mediawiki.api');
await new mw.Api().loadMessagesIfMissing(keys);
}
this.$diff.find('.diff-multi').append(
$('<div>').append(
$('<div>').append(
$('<a>').attr({
class: 'diffnow-multinext diffnow-switch',
href: mw.util.getUrl(this.oldTitle, {
diff: 'next',
oldid: this.oldId
}),
title: this.oldTitle
}).text(mw.msg('nextdiff'))
),
$('<div>').append(
$('<a>').attr({
class: 'diffnow-multiprev diffnow-switch',
href: mw.util.getUrl(this.newTitle, {
diff: 'prev',
oldid: this.newId
}),
title: this.newTitle
}).text(mw.msg('previousdiff'))
)
)
);
}
}
getAnchor($link) {
let isSwitch;
if ($link) {
isSwitch = $link.is(switchSelector);
} else {
$link = this.$link;
isSwitch = this.isSwitch;
}
if (isSwitch) {
return getDiff($link.closest('.diff')).$anchor;
}
return $link.closest('li, tr, .mw-history-compareselectedversions');
}
getContainer(create) {
let $container = this.$anchor.is('tr')
? this.$anchor.next('.diffnow-row').find('> td > .diffnow')
: this.$anchor.is('div')
? this.$anchor.next('.diffnow')
: this.$anchor.children('.diffnow');
if (create) {
if ($container.length) {
this.attachDiff($container);
} else {
$container = this.createContainter();
}
}
return $container;
}
attachDiff($container) {
let $oldDiff = $container.children('.diff');
if (!$oldDiff.is(this.$diff)) {
if ($oldDiff.length) {
getDiff($oldDiff).detachDiff();
}
$container.children('.diffnow-tools-top').after(this.$diff);
}
$container.find('.diffnow-difflink').attr('href', this.href);
}
createContainter() {
let $container = containers.pop();
if ($container) {
this.attachDiff($container);
return $container;
}
return $('<div>').addClass('diffnow').append(
$('<div>').addClass('diffnow-tools diffnow-tools-top').append(
$('<button>').attr({
class: 'diffnow-button diffnow-close oo-ui-icon-collapse',
title: 'Hide this diff'
})
),
this.$diff,
$('<div>').addClass('diffnow-tools diffnow-tools-bottom').append(
$('<button>').attr({
class: 'diffnow-button diffnow-scrollup oo-ui-icon-collapse',
title: 'Scroll to top'
}),
$('<button>').attr({
class: 'diffnow-button diffnow-scrolldown oo-ui-icon-expand',
title: 'Scroll to bottom'
}),
$('<button>').attr({
class: 'diffnow-button diffnow-close oo-ui-icon-close',
title: 'Hide this diff'
}),
$('<a>').attr({
class: 'diffnow-button diffnow-difflink oo-ui-icon-newWindow oo-ui-image-progressive',
href: this.href,
target: '_blank',
title: 'Open diff page'
})
)
)
.on('click', '.diffnow-close', this.collapse)
.on('click', '.diffnow-scrollup', this.scrollUp)
.on('click', '.diffnow-scrolldown', this.scrollDown)
.on(
'click keydown',
'.diffnow-context-collapsed > td, .diffnow-context-expanded > .diff-marker',
this.toggleContext
);
}
isVisible() {
return !!this.$diff?.[0].offsetParent;
}
markLink(visible = this.isVisible()) {
if (this.isSwitch) {
this.getContainer().toggleClass('diffnow-switching', !!this.aborter);
if (!this.$outerLink) return;
}
this.$outerLink
.toggleClass('diffnow-link-loading', !!this.aborter)
.toggleClass('diffnow-link-loaded', !this.aborter)
.toggleClass('diffnow-link-open', !this.isSwitch && visible);
}
markLinks() {
$(selector).filter((_, link) => {
let href = link.getAttribute('href');
return this.queries.has(href) || this.queries.has(sanitize(href));
}).addClass('diffnow-link-loaded');
if (!isHist) return;
$('.mw-history-compareselectedversions-button').toggleClass(
'diffnow-link-loaded',
!!getDiff(getHistHref())
);
}
markSeen() {
if (this.$link.is('.mw-rcfilters-ui-highlights-enhanced-nested:nth-child(n+2) .mw-changeslist-diff')) {
this.$anchor.nextAll().addBack()
.removeClass('mw-changeslist-watchedunseen mw-changeslist-line-watched mw-enhanced-watched')
.addClass('mw-changeslist-watchedseen mw-changeslist-line-not-watched');
return;
}
let $li = this.$anchor.closest('li, table');
let $unseen = $li.filter('.mw-changeslist-watchedunseen')
.add($li.find('.mw-changeslist-watchedunseen'));
if (!$unseen.length) return;
$unseen.removeClass('mw-changeslist-watchedunseen')
.addClass('mw-changeslist-watchedseen');
$li.filter('.mw-changeslist-line-watched')
.add($li.find('.mw-changeslist-line-watched'))
.removeClass('mw-changeslist-line-watched mw-enhanced-watched')
.addClass('mw-changeslist-line-not-watched');
}
async markNotLast(newDiff) {
this.isLast = false;
this.queries = new Set([...this.queries].filter(q => (
!/^title=|(?:^|[&?])(?:diff|oldid)=(?:0|cur)(?:&|$)/.test(q)
)));
this.$diff.find(
'.diffnow-mw-diff-ntitle1 a, .diffnow-mw-diff-ntitle1 .history-deleted'
).first().text(
newDiff.$diff.find(
'#mw-diff-otitle1 a, #mw-diff-otitle1 .history-deleted'
).first().text()
);
this.$diff.find('.diff-ntitle .mw-diff-edit a')
.attr('href', (_, href) => href + '&oldid=' + this.newId);
if (!mw.messages.exists('nextdiff')) {
await mw.loader.using('mediawiki.api');
await new mw.Api().loadMessagesIfMissing(['nextdiff']);
}
this.$diff.find('.diffnow-checknext')
.attr('class', 'diffnow-differences-nextlink')
.text(mw.msg('nextdiff'));
}
detachDiff() {
this.$diff?.detach();
this.markLink(false);
}
close(adjustFocus) {
this.detachDiff();
let $container = this.getContainer();
if ($container.length) {
let $row = $container.closest('.diffnow-row');
containers.push($container.detach());
$row.remove();
}
if (!adjustFocus) return;
setTimeout(() => {
this.$anchor.find('a[href]').last().each(function () {
this.focus();
this.blur();
});
});
}
collapse(e) {
e.preventDefault();
let $container = $(e.delegateTarget);
let diff = getDiff($container.children('.diff'));
if (diff.$link[0].getBoundingClientRect().top < 0) {
diff.$anchor[0].scrollIntoView();
$container.fadeOut('fast', () => {
diff.close(true);
setTimeout(() => {
diff.$anchor[0].scrollIntoView();
$container.removeAttr('style');
});
});
} else {
diff.close(true);
}
}
scrollUp(e) {
e.preventDefault();
let y = e.delegateTarget.getBoundingClientRect().top;
let ch = document.documentElement.clientHeight, buffer = ch / 2;
y -= y + 5 > buffer ? ch : buffer;
window.scrollBy({ top: y, behavior: 'smooth' });
}
scrollDown(e) {
e.preventDefault();
let y = e.delegateTarget.getBoundingClientRect().bottom;
let buffer = document.documentElement.clientHeight / 2;
if (y - 5 > buffer) {
y -= buffer;
}
window.scrollBy({ top: y, behavior: 'smooth' });
}
toggleContext(e) {
if (e.type === 'keydown' && (
e.which !== 13 && e.which !== 32 ||
e.ctrlKey || e.shiftKey || e.metaKey || e.altKey
)) {
return;
}
e.preventDefault();
let $row = $(e.target.closest('tr'));
if ($row.hasClass('diffnow-context-expanded')) {
$row.removeClass('diffnow-context-expanded')
.addClass('diffnow-context-collapsed')
.children().attr({
tabindex: 0,
role: 'button',
title: 'Expand'
});
expanded.delete($row[0]);
} else {
$row.removeClass('diffnow-context-collapsed')
.addClass('diffnow-context-expanded')
.children('.diff-context').removeAttr('tabindex role title')
.siblings('.diff-marker').attr('title', 'Collapse');
expanded.add($row[0]);
}
}
destroy() {
diffs.splice(diffs.indexOf(this), 1);
this.$link.removeClass('diffnow-link-loading diffnow-link-loaded diffnow-link-open');
this.getContainer().removeClass('diffnow-switching');
}
}
let filterContexts = $diff => {
$diff.find('.diff-context.diff-side-deleted > div').each(function () {
let $row = $(this.closest('tr'))
.removeClass('diffnow-context-expanded');
if (this.scrollHeight > this.clientHeight) {
if (expanded.has($row[0])) {
$row.addClass('diffnow-context-expanded')
.children('.diff-marker').attr({
tabindex: 0,
role: 'button',
title: 'Collapse'
});
} else {
$row.addClass('diffnow-context-collapsed').children().attr({
tabindex: 0,
role: 'button',
title: 'Expand'
});
}
} else {
$row.removeClass('diffnow-context-collapsed')
.children().removeAttr('tabindex role title');
}
});
};
let onResize = mw.util.debounce(() => {
filterContexts($('.diffnow > .diff'));
}, 250);
let notif;
let showError = async (href, msg) => {
notif = await mw.notify([
document.createTextNode(msg || `Couldn't load the diff`),
$('<p>').append(
$('<a>').attr({
href: href,
target: '_blank'
}).text('Open it in a new tab')
)[0]
], { autoHideSeconds: 'long', tag: 'diffnow', type: 'error' });
};
let getHistHref = () => {
let formData = new FormData(document.getElementById('mw-history-compare'));
return mw.util.getUrl(null, {
diff: formData.get('diff'),
oldid: formData.get('oldid')
});
};
$(document.body).on('click.diffnow', 'a, .mw-history-compareselectedversions-button', async function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
!this.matches(selector) && !this.matches(switchSelector)
) {
return;
}
e.preventDefault();
let $link = $(this);
let isCompare = $link.hasClass('mw-history-compareselectedversions-button');
let href = isCompare ? getHistHref() : this.pathname + this.search;
let diff = getDiff(href);
try {
if (diff) {
diff.setLink(href, $link);
return;
}
if (!isCompare && this.origin !== location.origin) {
throw '';
}
diff = new Diff(href, $link);
let url = new URL(href, location.href);
url.searchParams.set('diffonly', 1);
url.searchParams.set('action', 'render');
let promise = fetch(url, { signal: diff.aborter.signal });
mw.loader.using(['mediawiki.diff', 'mediawiki.diff.styles']);
if (notif) {
notif.close();
notif = null;
}
diff.append(await (await promise).text());
} catch (error) {
if (diff && !diff.attached) {
diff.destroy();
}
if (error.name === 'AbortError') {
notif = await mw.notify('Diff loading canceled', { tag: 'diffnow' });
} else if (error !== 'nonfatal') {
showError(href, error);
console.error(error);
}
}
});
let css = mw.loader.addStyleTag(`${selector} {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand.svg);
background-position: right;
background-repeat: no-repeat;
background-size: 10px 10px;
padding-right: 12px;
}
.mw-history-compareselectedversions-button {
background-position: right 6px center;
background-size: 14px 14px;
padding-right: 24px !important;
}
.skin-timeless .mw-history-compareselectedversions-button {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand-invert.svg);
}
.skin-timeless .mw-history-compareselectedversions-button.diffnow-link-loaded {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/reload-invert.svg) !important;
}
.skin-timeless .mw-history-compareselectedversions-button.diffnow-link-open {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/collapse-invert.svg) !important;
}
.diffnow-link-loading {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cstyle type='text/css'%3Ecircle%7Banimation:bounce-delay 600ms infinite ease-in-out both;animation-delay:-80ms;transform-origin:center;transform-box:fill-box%7Dcircle:first-of-type%7Banimation-delay:-160ms%7Dcircle:last-of-type%7Banimation-delay:0ms%7D@keyframes bounce-delay%7B0%25,50%25,100%25%7Btransform:scale(0)%7D20%25%7Bopacity:0.87;transform:scale(1)%7D%7D%3C/style%3E%3Ccircle cx='2.5' cy='10' r='2.5'/%3E%3Ccircle cx='10' cy='10' r='2.5'/%3E%3Ccircle cx='17.5' cy='10' r='2.5'/%3E%3C/svg%3E") !important;
}
.diffnow-link-loaded {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/reload.svg) !important;
}
.diffnow-link-open, .diffnow-context-expanded > .diff-marker {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/collapse.svg) !important;
}
.diffnow {
background: var(--background-color-base, #fff);
border: var(--border-base, 1px solid #a2a9b1);
border-radius: 4px;
position: relative;
}
.mw-history-compareselectedversions + .diffnow {
margin-top: 0.3em;
}
.diffnow-tools {
display: flex;
position: sticky;
background-color: var(--background-color-backdrop-light, rgba(255,255,255,0.65));
z-index: 1;
}
.diffnow-tools-top {
top: 0;
border-radius: 4px;
}
.diffnow-tools-bottom {
bottom: 0;
}
.diffnow-tools > .diffnow-button {
cursor: pointer;
height: 24px;
padding: 0;
box-sizing: content-box;
background-position: center;
background-repeat: no-repeat;
background-size: 16px 16px;
background-color: var(--background-color-transparent, transparent);
flex-grow: 1;
}
.diffnow-tools > .diffnow-button:hover {
background-color: var(--background-color-button-quiet--hover, rgba(0,24,73,0.027));
}
.diffnow-tools > .diffnow-button:active {
background-color: var(--background-color-button-quiet--active, rgba(0,24,73,0.082));
}
.diffnow-tools-top > .diffnow-button {
border: none;
border-radius: 3px 3px 0 0;
}
.diffnow-tools-bottom > .diffnow-button {
border: var(--border-base, 1px solid #a2a9b1);
border-radius: 99px;
margin: 4px;
height: 20px;
}
.diffnow-switching > .diff {
transition: opacity 200ms;
opacity: 0.4;
}
.diffnow-checknext, .diffnow-checknext:visited {
color: var(--color-subtle, #54595d);
}
.diffnow :not(.diffnow-context-expanded) > .diff-context > div {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.diffnow-context-collapsed > td,
.diffnow-context-expanded > .diff-marker {
cursor: pointer !important;
background-position: center;
background-repeat: no-repeat;
background-size: 12px 12px;
}
.diffnow-context-collapsed > .diff-marker {
background-image: url(/w/resources/lib/ooui/themes/wikimediaui/images/icons/expand.svg);
}
.diffnow-context-collapsed > .diff-context a {
pointer-events: none;
}
.diffnow .diff-multi > div {
display: flex;
justify-content: space-around;
}
.diffnow > .diff td div {
word-break: break-word;
}
.diffnow .mw-diff-inline-changed ins,
.diffnow .mw-diff-inline-changed del {
white-space: pre-wrap;
}
.diffnow-row .diffnow {
border: none;
}
.diffnow-row > td {
padding: 0 !important;
}
td.mw-changeslist-line-inner {
width: 100%;
}
.mw-enhanced-rc .diffnow > .diff td {
padding: 0.33em 0.5em;
}
.cdx-table__table-wrapper:has(.diffnow-row) {
overflow-x: visible;
}
span.mw-history-histlinks-current,
span.mw-history-histlinks-previous {
padding-right: 12px;
}
.client-js .diffnow .mw-anonuserlink {
padding-right: 0;
}`);
mw.loader.using(['oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-interactions']);
diffs._disable = () => {
$(document.body).off('.diffnow');
css.remove();
};
if (isHist) {
$(document.body).on('change.diffnow', '#mw-history-compare', function () {
let $buttons = $('.mw-history-compareselectedversions-button');
let diff = getDiff(getHistHref());
if (diff) {
$buttons.addClass('diffnow-link-loaded');
if ($buttons.is(diff.$link) && diff.isVisible()) {
diff.$link.addClass('diffnow-link-open');
} else {
$buttons.removeClass('diffnow-link-open');
}
} else {
$buttons.removeClass('diffnow-link-loaded diffnow-link-open');
}
});
}
let added, tempLoaded, ipInfoLoaded;
mw.hook('wikipage.content').add(async () => {
await new Promise(mw.requestIdleCallback);
if (!document.querySelector(selector)) return;
let modules = ['mediawiki.diff', 'mediawiki.diff.styles'];
let cspn = mw.config.get('wgCanonicalSpecialPageName');
let loadTemp = !tempLoaded &&
Number(mw.user.options.get('checkuser-temporary-account-enable')) &&
!['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(cspn);
if (loadTemp) {
modules.push('ext.checkUser.tempAccounts', 'ext.checkUser.styles');
}
if (Number(mw.user.options.get('checkuser-userinfocard-enable'))) {
modules.push('ext.checkUser.userInfoCard');
}
let loadIpInfo;
if (Number(mw.user.options.get('ipinfo-beta-feature-enable'))) {
modules.push('ext.ipInfo', 'ext.ipInfo.styles');
loadIpInfo = !ipInfoLoaded &&
['Contributions', 'DeletedContributions', 'IPContributions'].includes(cspn);
}
let promise = mw.loader.using(modules);
if (loadTemp || loadIpInfo) {
tempLoaded = loadTemp;
ipInfoLoaded = loadIpInfo;
await promise;
// https://gerrit.wikimedia.org/g/mediawiki/core/+/master/resources/src/startup/mediawiki.loader.js
let makeRequire = (moduleObj, basePath) => moduleName => {
let relParts = moduleName.match(/^((?:\.\.?\/)+)(.*)$/);
if (!relParts) {
return mw.loader.require(moduleName);
}
let baseDirParts = basePath.split('/');
baseDirParts.pop();
let prefixes = relParts[1].split('/');
prefixes.pop();
let prefix;
while ((prefix = prefixes.pop())) {
if (prefix === '..') {
baseDirParts.pop();
}
}
let fileName = baseDirParts.join('/');
if (fileName) {
fileName += '/';
}
fileName += relParts[2];
if (Object.hasOwn(moduleObj.packageExports, fileName)) {
return moduleObj.packageExports[fileName];
}
let scriptFiles = moduleObj.script.files;
if (!Object.hasOwn(scriptFiles, fileName)) {
throw Error('Cannot require undefined file ' + fileName);
}
let result;
let fileContent = scriptFiles[fileName];
if (typeof fileContent === 'function') {
let moduleParam = { exports: {} };
fileContent(makeRequire(moduleObj, fileName), moduleParam, moduleParam.exports);
result = moduleParam.exports;
} else {
result = fileContent;
}
moduleObj.packageExports[fileName] = result;
return result;
};
if (loadTemp) {
try {
let pack = mw.loader.moduleRegistry['ext.checkUser.tempAccounts'];
makeRequire(pack, pack.script.main)('./initOnHook.js')();
} catch {}
}
if (loadIpInfo) {
try {
let pack = mw.loader.moduleRegistry['ext.ipInfo'];
makeRequire(pack, pack.script.main)('./popup/init.js')();
} catch {}
}
}
if (added || !document.getElementById('p-cactions')) return;
added = true;
let callback = (records, observer) => {
if (records[0].target.classList.contains('diffnow-link-loaded')) {
observer.takeRecords();
observer.disconnect();
clickFirst();
}
};
let clickFirst = () => {
let link = document.querySelector(selector);
if (!link || link.matches('.diffnow-link-loaded, [href*="&diff=cur&"], .consecudiff > a')) {
return;
}
new MutationObserver(callback).observe(link, { attributeFilter: ['class'] });
link.click();
};
mw.util.addPortletLink('p-cactions', '#', 'Expand all diffs').firstElementChild.addEventListener('click', e => {
e.preventDefault();
clickFirst();
});
});
});
// window.smpg?._disable?.();
mw.loader.using('mediawiki.util', async function smoothPager() {
let action = mw.config.get('wgAction');
let isHist = action === 'history';
let isPerma = action === 'view' &&
['oldid', 'diff'].some(s => mw.util.getParamValue(s));
let ns = !isHist && !isPerma && mw.config.get('wgNamespaceNumber');
let isFile = ns === 6;
let isCat = ns === 14;
let cspn = ns === -1 && mw.config.get('wgCanonicalSpecialPageName');
if (!(isHist || isPerma || isFile || isCat || cspn)) return;
let isSearch = cspn === 'Search';
let isWl = cspn === 'Watchlist';
let isRc = cspn === 'Recentchanges' || cspn === 'Recentchangeslinked';
if ((isWl || isRc) && document.body.classList.contains('mw-rcfilters-enabled')) {
return;
}
let isContribs = cspn === 'Contributions' || cspn === 'IPContributions';
let o = {
useRender: isCat || isWl || isRc,
selector: isPerma ? '#bodyContent' :
isFile ? '#mw-imagepage-section-filehistory' :
isCat ? '.mw-category-generated' :
'#mw-content-text',
outerSelectors: [],
linkSelector: isPerma ? '#differences-prevlink, #differences-nextlink, #mw-diff-ntitle1 > strong > a, #mw-diff-otitle1 > strong > a, #mw-revision-nav > a, .fr-diff-to-stable > a' :
isCat ? '#mw-subcategories > a, #mw-pages > a, #mw-category-media > a' :
isWl ? '#ca-nstab-special > a, .mw-watchlist-toollink-active > a, #p-associated-pages .selected > a' :
isRc ? '#ca-nstab-special > a, .rclinks > a, .rcshowhideoption > a, .rclistfrom > a' :
`#ca-nstab-special > a, .mw-pager-navigation-bar > a, .TablePager_nav > .oo-ui-widget-enabled > a, .mw-datatable th > a, .cdx-table-pager .cdx-button--fake-button--enabled, .cdx-table__table__cell--has-sort > a, .mw-prefixindex-nav > a, .mw-allpages-nav > a, .CategoryTreeParents .CategoryTreeLabel, .mw-special-Newpages form div > a, .mw-abusefilter-history-buttons a${isHist ? ', #ca-history > a' : isSearch ? ', .search-types a, .searchdidyoumean a' : ''}`,
canPost: isSearch || [
'AbuseFilter', 'ExpandTemplates', 'TemplateSandbox'
].includes(cspn),
paramRe: isPerma && /^(?:diff|diffonly|direction|oldid)$/ ||
isSearch && /^(?:advancedSearch-current|limit|offset|profile|runsuggestion|search|sort|ns\d+)$/,
reruns: [
'mediawiki.action.history',
'mediawiki.special.search',
'ext.advancedSearch.init',
'mediawiki.special.watchlist',
'mediawiki.special.watchlistedit',
'mediawiki.pager.codex',
'mediawiki.misc-authed-curate',
'mediawiki.misc-authed-ooui',
'mediawiki.misc-authed-pref',
'mediawiki.special.unwatchedPages',
'ext.thanks.corethank',
'ext.flaggedRevs.review',
'ext.gadget.watchlist-notice-core'
],
pages: []
};
if (isContribs) {
o.reruns.push('ext.ipInfo');
}
if (!o.useRender) {
o.outerSelectors.push('#firstHeading', '.mw-indicators');
if (!isPerma && !isFile) {
o.outerSelectors.push('#mw-content-subtitle');
}
}
o.formSelector = isHist && '#mw-history-searchform' ||
isSearch && '#search, #powersearch, #searchform' ||
cspn && `#mw-content-text form${o.canPost ? '': ':not([method="post"])'}`;
if (o.formSelector) {
o.formSelector += ', .cdx-table-pager__limit-form';
}
window.smpg = o;
let getHref = () => location.pathname + location.search;
let getKey = (href, isPost) => {
if (o.paramRe) {
let params = new URLSearchParams();
new URL(href, location.href).searchParams.forEach((v, k) => {
if (o.paramRe.test(k)) {
params.set(k, v);
}
});
params.sort();
return String(params);
}
if (isPost) {
return href + '??' + Date.now();
}
return href;
};
class Page {
constructor(href, key) {
this.href = href || getHref();
this.key = key || getKey(this.href);
this.keys = new Set().add(this.key);
o.pages.push(this);
}
init($response) {
if ($response) {
if (isWl || isRc) {
this.$cont = $(document.querySelector(o.selector).cloneNode())
.removeClass('smoothpager-switching')
.append($response);
} else {
let $cont = $response.filter(o.selector).not('script');
if (!$cont.length) {
$cont = $response.find(o.selector).not('script');
if (!$cont.length) {
throw 'Content not found';
}
}
$cont.find('script').remove();
this.$cont = $($cont[0]);
}
this.attached = 0;
} else {
if (this.$cont) return;
this.$cont = $(document.querySelector(o.selector));
this.attached = 1;
}
this.getConfigAndModules($response);
this.getOuterEls($response);
this.getDocTitle($response);
this.getRev($response);
}
getConfigAndModules($response) {
if (o.useRender) return;
if (!$response) {
this.config = Object.assign({}, window.RLCONF);
}
let $scripts = $response
? $response.filter('script').remove()
: $('head > script, body > script');
$scripts.map(function () {
return this.textContent.match(/\bmw\.config\.set\({.+}\);/g);
}).each((_, s) => {
let obj;
do {
try {
obj = JSON.parse(s.slice(14, -2));
} catch {
let i = s.slice(0, -3).lastIndexOf('});');
if (i === -1) return;
s = s.slice(0, i + 3);
}
} while (!obj);
this.config = Object.assign(this.config || {}, obj);
});
if (!$response) return;
let match = $scripts.first().text().replaceAll('\n', '')
.match(/;RLCONF=({.+});RLSTATE=({.+});RLPAGEMODULES=(\[.+\]);$/);
if (match) {
try {
this.config = Object.assign(this.config || {}, JSON.parse(match[1]));
} catch {}
this.modules = [];
try {
this.modules.push(...Object.keys(JSON.parse(match[2])));
} catch {}
try {
this.modules.push(...JSON.parse(match[3]));
} catch {}
}
this.redirect();
}
setConfig() {
if (!this.config) return;
mw.config.set(this.config);
if (!o.cur.config) return;
Object.entries(o.cur.config).forEach(([k, v]) => {
if (!Object.hasOwn(this.config, k) &&
JSON.stringify(mw.config.get(k)) === JSON.stringify(v)
) {
delete mw.config.values[k];
}
});
}
getOuterEls($response) {
this.outerEls = o.outerSelectors.map(selector => (
$response ? $response.find(selector)[0] : document.querySelector(selector)
)).map(el => el && !this.$cont[0].contains(el) && el);
}
setOuterEls() {
o.outerSelectors.forEach((selector, i) => {
let outer = document.querySelector(selector);
if (!outer) return;
$(outer).before(this.outerEls[i] || outer.cloneNode()).detach();
});
}
getDocTitle($response) {
if (o.useRender) return;
if ($response) {
let $title = $response.filter('title');
if ($title.length) {
this.docTitle = $title.text();
}
} else {
this.docTitle = document.title;
}
}
setDocTitle() {
if (this.docTitle) {
document.title = this.docTitle;
} else if (this.outerEls[0] && o.cur.outerEls?.[0]) {
document.title = document.title.replace(
o.cur.outerEls[0].textContent.trim(),
this.outerEls[0].textContent.trim()
);
}
}
getRev($response) {
if (!isPerma) return;
let params = new URLSearchParams(this.key);
if (params.has('diff')) {
if ($response) {
let dir = params.get('diff') === 'next' ? 'prev' : 'next';
let id = this.config?.[
dir === 'prev' ? 'wgDiffNewId' : 'wgDiffOldId'
];
if (!id) {
id = this.findParam(
'oldid',
dir === 'prev'
? '#mw-diff-ntitle1 > strong > a, #differences-prevlink'
: '#mw-diff-otitle1 > strong > a, #differences-nextlink'
);
}
if (id) {
this.rev = dir === 'prev' ? id : params.get('oldid');
params.set('diff', dir);
params.set('oldid', id);
this.keys.add(String(params));
}
} else {
this.rev = mw.config.get('wgDiffNewId');
let pn = this.findParam(
'title',
'#mw-diff-ntitle1 > strong > a, #differences-nextlink'
);
if (pn === mw.config.get('wgPageName') &&
!this.$cont[0].querySelector('.diff[data-mw-interface] .diff-multi')
) {
params.set('diff', 'prev');
params.set('oldid', this.rev);
this.keys.add(String(params));
let oldId = mw.config.get('wgDiffOldId');
if (oldId) {
params.set('diff', 'next');
params.set('oldid', oldId);
this.keys.add(String(params));
}
}
}
} else {
if ($response) {
if (params.has('direction')) {
let id = this.config?.wgRevisionId;
if (!id) {
id = this.findParam(
'oldid',
'#t-permalink > a, #p-tb .menu__item--page-actions-overflow-permalink',
$response
);
}
if (id) {
this.rev = id;
this.keys.add('oldid=' + id);
o.cur.keys.add(`direction=${
params.get('direction') === 'next' ? 'prev' : 'next'
}&oldid=${id}`);
}
} else {
this.rev = params.get('oldid');
}
} else {
this.rev = mw.config.get('wgRevisionId');
if (params.has('direction')) {
this.keys.add('oldid=' + this.rev);
}
}
}
}
findParam(param, query, $range = this.$cont) {
let search = $range.find(query).prop('search');
return search && mw.util.getParamValue(param, search);
}
updateLinks() {
if (this.rev) {
$('#ca-edit > a').attr('href', mw.util.getUrl(null, {
action: 'edit',
oldid: this.rev
}));
$('#t-permalink > a, #p-tb .menu__item--page-actions-overflow-permalink')
.attr('href', mw.util.getUrl(null, { oldid: this.rev }));
}
$('#ca-nstab-special > a').attr('href', this.href);
$('.printfooter > a').attr('href', location.href).text(location.href);
$('#footer-places-mobileview > a, #mw-mf-display-toggle').attr('href', function () {
let url = new URL(location.href);
url.searchParams.set(
'mobileaction',
mw.util.getParamValue('mobileaction', this.search)
);
return '//' + this.host + url.pathname + url.search;
});
$('#t-urlshortener > a').attr('href', function () {
let params = new URLSearchParams(this.search);
params.set('url', location.href);
return this.pathname + '?' + params;
});
}
redirect() {
if (this.config.wgAction !== mw.config.get('wgAction') ||
this.config.wgPageName?.replace(/\/.*/, '') !== mw.config.get('wgPageName').replace(/\/.*/, '')
) {
location.href = this.href;
throw 'redirect';
}
}
replaceContent(isPopState, isRefresh) {
let oldCont = document.querySelector(o.selector);
if (this.$cont.is(oldCont)) {
throw 'Attempt to replace content with itself';
}
if (!isPopState && !isRefresh) {
this.pushState();
}
$(oldCont).before(this.$cont).detach();
console.log(this.key, o);
this.setOuterEls();
this.setDocTitle();
this.setConfig();
if (isSearch && this.attached) {
let input = this.$cont[0].querySelector('#searchText > input');
if (input) {
input.value = input.defaultValue;
}
}
o.cur = this;
this.loadModules(isPopState);
this.updateLinks();
}
pushState() {
history.pushState({ _smpg: this.key }, '', this.href);
history.replaceState({ _smpg: this.key }, '', this.href);
}
async loadModules(isPopState) {
o.pending = true;
try {
await mw.loader.using(this.modules || []);
} catch {} finally {
o.pending = false;
if (!this.attached) {
this.fireHooks();
this.rerunModules();
}
if (!isPopState) {
this.scroll();
}
this.attached++;
}
}
fireHooks() {
mw.hook('wikipage.content').fire(this.$cont);
this.$cont.find('.diff[data-mw-interface]').each(function () {
mw.hook('wikipage.diff').fire($(this));
});
this.$cont.find('.catlinks[data-mw-interface]').each(function () {
mw.hook('wikipage.categories').fire($(this));
});
mw.hook('htmlform.enhance').fire(this.$cont);
}
rerunModules() {
o.reruns.forEach(m => {
let pack = mw.loader.moduleRegistry[m];
if (!pack || pack.state !== 'ready') return;
if (typeof pack.script === 'function') {
pack.script($, $, mw.loader.require, pack.module);
return;
}
delete mw.loader.moduleRegistry[m];
mw.loader.implement(
m + '@' + pack.version, pack.script, pack.style,
pack.messages, pack.templates, pack.deprecationWarning
);
});
if ((isHist || isContribs || isRc) && window.Twinkle) {
window.Twinkle.rollback();
}
}
scroll() {
let form = o.formSelector && this.$cont[0].querySelector(o.formSelector);
if (form) {
let y = form.getBoundingClientRect().bottom;
if (y < 0) {
window.scrollBy(0, y);
}
} else if (this.$cont[0].getBoundingClientRect().top < 0) {
this.$cont[0].scrollIntoView();
}
}
}
o.cur = new Page();
let getContent = async (href, state, options) => {
if (o.pending) return;
o.aborter?.abort();
let key = state?._smpg || getKey(href, !!options);
let isRefresh = key === o.cur.key;
if (state && isRefresh) return;
let oldCont = document.querySelector(o.selector);
if (!oldCont) {
notify(href, 'No element to replace', 'error');
return;
}
let page = o.pages.find(p => p.keys.has(key));
if ((state || !isRefresh) && page) {
page.href = href;
page.replaceContent(!!state, isRefresh);
return;
}
oldCont.classList.add('smoothpager-switching');
let $throbber = $('<div>').addClass('smoothpager-throbber')
.appendTo(document.body);
if (!isRefresh && o.pages.length === 1) {
o.cur.init();
}
o.aborter = new AbortController();
let url = href;
if (o.useRender) {
url = new URL(href, location.href);
url.searchParams.set('action', 'render');
}
let promise = fetch(url, Object.assign({
signal: o.aborter.signal
}, options));
if (notif) {
notif.close();
notif = null;
}
try {
let response = await (await promise).text();
if (page) {
page.href = href;
} else {
page = new Page(href, key);
}
page.init($($.parseHTML(response, !o.useRender)));
page.replaceContent(!!state, isRefresh);
} catch (e) {
if (e.name === 'AbortError') {
if (state && o.cur) {
o.cur.pushState();
}
} else if (e === 'redirect') {
notify(href, 'Redirecting...');
} else {
notify(href, e || `Couldn't load the page`, 'error');
console.error(e);
}
} finally {
o.aborter = null;
oldCont.classList.remove('smoothpager-switching');
$throbber.remove();
}
};
let notif;
let notify = async (href, msg, type) => {
notif = await mw.notify([
document.createTextNode(msg),
$('<p>').append(
$('<a>').attr({
href: href,
target: '_blank'
}).text('Open it in a new tab')
)[0]
], {
autoHideSeconds: 'long',
tag: 'smoothpager',
type: type
});
};
let onClick = function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
!this.matches(o.linkSelector) || this.origin !== location.origin
) {
return;
}
e.preventDefault();
getContent(this.pathname + this.search);
};
let onKeyDown = e => {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
e.target.closest(':read-write')
) {
return;
}
switch (e.which) {
case 27:
o.aborter?.abort();
break;
case 110:
case 190:
e.preventDefault();
getContent(getHref());
}
};
let onPopState = e => {
let state = e.originalEvent.state;
if (state && !state._smpg) return;
history.replaceState(state, '', location.href);
getContent(getHref(), state || {});
};
let onSubmit = e => {
if (e.originalEvent && (
e.originalEvent.defaultPrevented || !e.originalEvent.isTrusted
) || !o.canPost && e.target.method !== 'get' ||
!e.target.matches(o.formSelector)
) {
return;
}
let path = e.target.getAttribute('action');
let isScript = path === mw.config.get('wgScript');
let comps = [mw.config.get('wgPageName')];
if (mw.config.get('wgNamespaceNumber') === -1 && comps[0].includes('/')) {
comps.push(comps[0].replace(/\/.*/, ''));
}
comps.push((comps[1] || comps[0]) + '/');
if (!isScript && !comps.some(c => mw.util.getUrl(c) === path)) return;
let formData = new FormData(e.target);
if (isScript && !comps.includes(formData.get('title').replaceAll(' ', '_'))) {
return;
}
e.preventDefault();
e.stopPropagation();
if (isSearch && e.target.id !== 'searchform') {
formData.set('search', OO.ui.infuse($('#searchText')).getValue());
} else if (e.target.method === 'post') {
let submitter = e.originalEvent?.submitter;
if (submitter?.name) {
formData.append(submitter.name, submitter.value);
}
getContent(path, null, {
method: 'POST',
headers: { 'Content-Type': e.target.enctype },
body: e.target.enctype === 'multipart/form-data'
? formData
: new URLSearchParams(formData)
});
return;
}
getContent(path + '?' + new URLSearchParams(formData));
};
let setPortlet = text => {
if (!o.portletLink) return;
$(o.portletLink).find('*').addBack().contents().each(function () {
if (this.nodeType === 3 && this.textContent.trim()) {
this.textContent = text;
return false;
}
});
};
o._enable = () => {
o.enabled = true;
let $body = $(document.body).on('click', 'a', onClick);
if (o.formSelector) {
$body.on('submit', onSubmit);
}
$body.parent().on('keydown', onKeyDown);
$(window).on('popstate', onPopState);
if (o.css) {
o.css.disabled = false;
}
if (isPerma) {
mw.trackSubscribe('counter.MediaWiki.RevisionSlider.event.init', o._disable);
}
setPortlet('Disable SmoothPager');
};
o._disable = () => {
o.enabled = false;
$(document.body).off('click', onClick).off('submit', onSubmit)
.parent().off('keydown', onKeyDown);
$(window).off('popstate', onPopState);
o.css.disabled = true;
mw.trackUnsubscribe(o._disable);
setPortlet('Enable SmoothPager');
};
o._enable();
o.css = mw.loader.addStyleTag(`${o.linkSelector},
.cdx-table__table__sort-button {
color: #008064 !important;
}
:is(${o.linkSelector}, .cdx-table__table__sort-button):visited {
color: #006400 !important;
}
:is(${o.linkSelector}, .cdx-table__table__sort-button):active {
color: #faa700 !important;
}
.cdx-table-pager .cdx-button--fake-button--enabled > .cdx-button__icon {
background-color: #14866d;
}
.smoothpager-switching {
opacity: 0.75;
}
.smoothpager-throbber {
width: 20%;
height: 0.5vh;
position: fixed;
top: 0;
left: 0;
background-color: var(--background-color-progressive, #36c);
transform: translate(-100%);
animation: smoothpager-throbber 1s infinite linear;
}
@keyframes smoothpager-throbber {
to {
transform: translate(700%);
}
}
@media (prefers-reduced-motion: reduce) {
.smoothpager-throbber {
animation: smoothpager-throbber 1s infinite steps(10,end) !important;
width: 40%;
}
}${o.formSelector ? `
:is(${o.formSelector}) input[type="submit"],
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
color: var(--color-inverted, #fff);
background-color: #14866d;
border-color: #14866d;
}
:is(${o.formSelector}) input[type="submit"]:hover,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover {
background-color: #00af89;
border-color: #00af89;
}
:is(${o.formSelector}) input[type="submit"]:focus,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
border-color: #14866d;
}
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
box-shadow: inset 0 0 0 1px #14866d, inset 0 0 0 2px #fff;
}
:is(${o.formSelector}) input[type="submit"]:active,
:is(${o.formSelector}) .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active {
background-color: #0e725a;
border-color: #0e725a;
box-shadow: none;
}` : ''}`);
await $.ready;
if (!document.getElementById('p-cactions')) return;
o.portletLink = mw.util.addPortletLink('p-cactions', '#', 'Disable SmoothPager').firstElementChild;
o.portletLink.addEventListener('click', e => {
e.preventDefault();
o[o.enabled ? '_disable' : '_enable']();
});
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], function smartDiff() {
mw.loader.addStyleTag('.smartdiff-link.extiw, .smartdiff-link.external{color:var(--color-progressive,#36c)} .smartdiff-link.extiw:visited, .smartdiff-link.external:visited{color:#795cb2} .smartdiff-link.extiw:active, .smartdiff-link.external:active{color:#faa700}');
class SmartDiff {
constructor($diff) {
this.$diff = $diff;
this.isSpecial = mw.config.get('wgNamespaceNumber') === -1;
this.isView = mw.config.get('wgAction') === 'view' &&
new URLSearchParams(location.search).get('diffonly') !== '1';
this.magicWords = [
'!', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME',
'CURRENTDOW', 'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1',
'CURRENTMONTHABBREV', 'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN',
'CURRENTTIME', 'CURRENTTIMESTAMP', 'CURRENTVERSION', 'CURRENTWEEK',
'CURRENTYEAR', 'DEFAULTCATEGORYSORT:', 'DEFAULTSORT:', 'DEFAULTSORTKEY:',
'DISPLAYTITLE:', 'filepath:', 'formatnum:', 'FULLPAGENAME',
'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'int:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2',
'LOCALDAYNAME', 'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1',
'LOCALMONTHABBREV', 'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME',
'LOCALTIMESTAMP', 'LOCALWEEK', 'LOCALYEAR', 'msg:', 'msgnw:',
'NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:', 'NAMESPACENUMBER',
'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS', 'NUMBEROFARTICLES',
'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES', 'NUMBEROFUSERS',
'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:', 'PAGESINCATEGORY:',
'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2', 'REVISIONDAY2:',
'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:',
'TALKSPACEE', 'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
if (window.smartdiffMagicWords) {
this.magicWords.push(...window.smartdiffMagicWords);
}
try {
this.subNs = mw.config.get('wgVisualEditorConfig').namespacesWithSubpages;
} catch (e) {}
if (!this.subNs) {
this.subNs = Object.keys(mw.config.get('wgFormattedNamespaces'))
.map(k => Number(k)).filter(ns => ![0, 6, 8].includes(ns));
}
this.re = /((?:\[(?:<[^>]*>)?\[|(?<!{(?:<[^>]*>)?){(?:<[^>]*>)?{(?:<[^>]*>)?(?:(?:#(?:<[^>]*>)?invoke|(?:safe)?subst|msg(?:nw)?|raw|int)(?:<[^>]*>)?:)?)(?:\s*(?:<[^>]*>)?<(?:<[^>]*>)?tvar(?:<[^>]*>)?\s(?!>).*?>)?\s*)((?:(?!&[gl]t;)[^\[\]{|}])+?)(?=\s*(?:(?:<[^>]*>)?<(?:<[^>]*>)?\/(?:<[^>]*>)?tvar(?:<[^>]*>)?>(?:<[^>]*>)?\s*)?(?:\||\](?:<[^>]*>)?\]|}(?:<[^>]*>)?}|$))/g;
this.headRe = /^((?:(?:<[^>]*>)*=){1,6}(?:<[^>]*>)?\s*)((?:(?!&[gl]t;).)+?)(?=\s*(?:(?:<[^>]*>)?=){1,6}(?:<[^>]*>|\s)*(?:<|$))/g;
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
this.galleryRe = /^(\s*)((?:(?!&[gl]t;)[^\[\]{|}])+\.(?:<[^>]*>)?(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))(?=\s*(?:(?:<[^>]*>)?(?:<[^>]*>)?\s*)?(?:\||$))/gi;
this.urlRe = /(\[(?:<[^>]*>)?(?=.+\]))?((\bhttps?(?:<[^>]*>)?:)?(?:<[^>]*>)?\/(?:<[^>]*>)?\/(?:<[^>]*>|(?!&[gl]t;)[^\s"<>\[\]{|}])+)/g;
if (window.smartdiffTemplates) {
this.tempRe = /( data-smartdiff-temp="(\d+)">[^{|}]+)(\|(?:(?!&[gl]t;)[^\[\]{}]|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?})+)(?=}(?:<[^>]*>)?}|$)/g;
this.tempSubRe = /((?:\s|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?}[^<>|]*|<[^>]*>)*(?:\|(?:\s|(?:<[^>]*>)|\d+(?:\s|<[^>]*>)*=|[^\d<=>|](?:[^<=>|]|<[^>]*>)*=(?:[^<=>|]|<[^>]*>)*\|?)*|$))/;
this.templates = window.smartdiffTemplates;
}
this.side = 'old';
$diff.find('.diff-deletedline > div').get().forEach(this.processDiv);
this.side = 'new';
$diff.find('.diff-addedline > div').get().forEach(this.processDiv);
let $contexts = $diff.find('.diff-context > div');
$contexts.each((i, div) => {
if (i % 2) {
this.side = 'new';
if (this.propUsed && this.getProp() !== this.getProp('pn', 'old')) {
this.processDiv(div);
} else {
$contexts.eq(i).replaceWith($contexts.eq(i - 1).clone());
}
} else {
this.side = 'old';
this.propUsed = false;
this.processDiv(div);
}
});
this.links = {};
$diff.find('.smartdiff-link:not(.external)').each((i, link) => {
let title = link.title;
if (!title) return;
if (!this.links.hasOwnProperty(title)) {
this.links[title] = [];
}
this.links[title].push(link);
});
this.query(Object.keys(this.links).slice(0, 500));
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
}
processDiv = div => {
if (div.querySelector('a[href]')) return;
let origHtml = div.innerHTML;
let newHtml = origHtml.replace(this.urlRe, this.urlRep)
.replace(this.galleryRe, this.galleryRep)
.replace(this.re, this.rep).replace(this.headRe, this.headRep);
if (this.tempRe) {
newHtml = newHtml.replace(this.tempRe, this.tempRep);
}
if (newHtml === origHtml) return;
newHtml = newHtml.replace(/<(ins|del)(?: [^>]+)?><\/\1>/g, '');
let $newDiv = $('<div>').html(newHtml);
if (this.detectErrors($newDiv, newHtml, origHtml, div)) return;
div.textContent = '';
$newDiv.contents().appendTo(div);
};
rep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
let t = mw.Title.newFromText(s), isTemp;
if (t) {
if ($1.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s[0] === '/') {
if (this.subNs.includes(this.getProp('ns'))) {
t = mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
} else if ($1[0] === '{') {
t.namespace = 10;
}
} else if ($1[0] === '{') {
if (s[0] === '#') {
return $0;
}
if ($1.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (!t.namespace && s[0] !== ':') {
if (!$1.includes('msg') && !$1.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && this.magicWords.includes(match[0])) {
return $0;
}
}
t.namespace = 10;
isTemp = true;
}
} else if ((this.isSpecial || !this.isView) && s[0] === '#') {
t.title = this.getProp();
}
} else if (s.startsWith('../') && this.subNs.includes(this.getProp('ns'))) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
t = mw.Title.newFromText(sub ? sup + '/' + sub : sup);
}
}
if (!t) {
return $0;
}
let attrs = {
class: 'smartdiff-link',
href: t.getUrl()
};
if (this.isSpecial || !this.isView || s[0] !== '#') {
attrs.title = t.toText();
}
if (isTemp && this.tempRe) {
let name = t.getMainText();
let idx = this.templates.findIndex(temp => temp.names.includes(name));
if (idx !== -1) {
attrs['data-smartdiff-temp'] = idx;
}
}
return pre + $('<a>').attr(attrs).html(mid)[0].outerHTML + post;
};
stripTags(s, decode, pre = '', post = '') {
let mid = s, tags = s.match(/<\/?(?:ins|del)(?: [^>]+)?>/g);
s = $($.parseHTML(s.replace(/&/g, '&'))).text();
if (decode) {
try {
s = decodeURIComponent(s);
} catch (e) {}
}
if (tags) {
if (tags[0][1] === '/') {
pre += tags[0];
mid = `<${tags[0].slice(2, 5)} class="diffchange diffchange-inline">` + mid;
}
let lastTag = tags.pop();
if (lastTag[1] !== '/') {
mid += `</${lastTag.slice(1, 4)}>`;
post = lastTag + post;
}
}
return [s, pre, mid, post];
}
headRep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
s = s.replace(/'''(.+?)'''|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>/gi, '$1')
.replace(/''(.+?)''/g, '$1')
.replace(/^_+|_+$/g, '');
let t = mw.Title.newFromText(
`${this.isSpecial || !this.isView ? this.getProp() : ''}#${s}`
);
if (!t) {
return $0;
}
let attrs = {
class: 'smartdiff-link',
href: t.getUrl()
};
if (this.isSpecial || !this.isView) {
attrs.title = t.toText();
}
return pre + $('<a>').attr(attrs).html(mid)[0].outerHTML + post;
};
galleryRep = ($0, $1, $2) => {
if ($0.includes('<a class="smartdiff-link')) {
return $0;
}
let [s, pre, mid, post] = this.stripTags($2, true, $1);
let t = mw.Title.newFromText(s, 6);
if (!t) {
return $0;
}
return pre + $('<a>').attr({
class: 'smartdiff-link',
href: t.getUrl(),
title: t.toText()
}).html(mid)[0].outerHTML + post;
};
urlRep = ($0, $1, $2, $3) => {
let main = $2, trail;
if (!$1) {
if (!$3) {
return $0;
}
let re = main.includes('(')
? /[!,.:;?](?:<[^>]*>)?$/
: /[!),.:;?](?:<[^>]*>)?$/;
let match = main.match(re);
if (match &&
!/&(?:;(?:<[^>]*>)?(?:[a-z]+|(?:#(?:<[^>]*>)?(?:x[\da-f]|\d+)))(?:<[^>]*>)?)?;$/i.test(main)
) {
trail = match[0];
main = main.slice(0, -trail.length);
}
}
let [url, pre, mid, post] = this.stripTags(main);
if ($1) {
pre = $1 + pre;
} else if (trail) {
post += trail;
}
return pre + $('<a>').attr({
class: 'smartdiff-link external',
href: url,
rel: 'nofollow'
}).html(mid)[0].outerHTML + post;
};
tempRep = ($0, $1, $2, $3) => {
if ($3.includes('<a class="smartdiff-link')) {
return $0;
}
let temp = this.templates[$2];
return $1 + $3.split(this.tempSubRe).map((os, i) => {
if (!os || i % 2) {
return os;
}
let j = i / 2;
if (j < temp.start || j > temp.end ||
temp.skipOdd && j % 2 || temp.skipEven && j % 2 === 0
) {
return os;
}
let [s, pre, mid, post] = this.stripTags(os, true);
if (temp.prefix) {
s = temp.prefix + s;
}
if (temp.suffix) {
s += temp.suffix;
}
let t = temp.forceNs
? mw.Title.makeTitle(temp.namespace, s)
: mw.Title.newFromText(s, temp.namespace);
if (!t) {
return os;
}
let params = (j >= temp.noRedirectStart || j <= temp.noRedirectEnd) &&
{ redirect: 'no' };
return pre + $('<a>').attr({
class: 'smartdiff-link',
href: t.getUrl(params),
title: t.toText()
}).html(mid)[0].outerHTML + post;
}).join('');
};
getProp(n = 'pn', side = this.side) {
this.propUsed = true;
if (this[side]) {
if (this[side][n]) {
return this[side][n];
}
} else {
this[side] = {};
let link = this.$diff[0].querySelector(
side === 'old'
? '#mw-diff-otitle1 a, #differences-prevlink'
: '#mw-diff-ntitle1 a, #differences-nextlink'
);
if (link) {
let pn = mw.util.getParamValue('title', link.search);
this[side].pn = pn;
this[side].ns = mw.Title.newFromText(pn).namespace;
return this[side][n];
}
}
if (this[n]) {
return this[n];
}
if (this.isSpecial) {
this.pn = '';
this.ns = 0;
} else {
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
}
return this[n];
}
query(titles) {
if (!titles.length) return;
new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
}).then(response => {
let query = response && response.query;
if (!query) return;
let data = {};
(query.pages || []).forEach(page => {
let obj = { classes: page.linkclasses || [] };
if (page.missing && !page.known) {
obj.classes.push('new');
obj.params = { action: 'edit', redlink: 1 };
}
data[page.title] = obj;
});
(query.interwiki || []).forEach(interwiki => {
data[interwiki.title] = {
classes: ['extiw'],
url: interwiki.url
};
});
(query.normalized || []).forEach(entry => {
if (!data.hasOwnProperty(entry.to)) return;
let obj = data[entry.to];
obj.canonical = entry.to;
if (!obj.url) {
obj.url = mw.util.getUrl(entry.to, obj.params);
}
data[entry.from] = obj;
});
Object.entries(data).forEach(([title, obj]) => {
if (!this.links.hasOwnProperty(title)) return;
let $links = $(this.links[title]).addClass(obj.classes)
.attr('title', obj.canonical);
if (obj.url) {
$links.attr('href', function () {
return obj.url + this.hash;
});
}
});
this.query(titles.slice(50));
});
}
detectErrors($newDiv, newHtml, origHtml, div) {
let comp = $newDiv.html();
if (comp !== newHtml) {
console.warn(
'SmartDiff syntax error at:\n',
div,
`\nNew HTML:\n${newHtml}\nCompared against:\n${comp}`
);
this.hasError = true;
return true;
}
let $comp = $newDiv.clone();
$comp.find('.smartdiff-link').contents().unwrap();
comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
console.warn(
'SmartDiff mutation error at:\n',
div,
`\nOriginal HTML:\n${origHtml}\nCompared against:\n${comp}`
);
this.hasError = true;
return true;
}
}
}
mw.hook('wikipage.diff').add($diff => {
new SmartDiff($diff);
});
});
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopySectLink.js&action=raw&ctype=text/javascript', 's');
mw.loader.using([
'ext.visualEditor.desktopArticleTarget.init', 'mediawiki.storage'
], function ipaInput() {
if (!mw.libs.ve.isVisualAvailable &&
!['edit', 'submit'].includes(mw.config.get('wgAction'))
) {
return;
}
mw.loader.addStyleTag(`.oo-ui-icon-schwa{background-image:url("data:image/svg+xml,%3Csvg width='20' height='20' version='1.1' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m0 3v14h4v-2h-2v-10h2v-2zm16 0v2h2v10h-2v2h4v-14zm-6 2c-2.41 0-4.43 1.73-4.9 4h2.08c0.41-1.17 1.5-2 2.82-2 1.67 0 3 1.33 3 3h-8c0 2.75 2.25 5 5 5 2.75 0 5-2.25 5-5 0-2.75-2.25-5-5-5zm-2.59 6.5h5.18c-0.516 0.895-1.47 1.5-2.59 1.5-1.12 0-2.07-0.605-2.59-1.5z'/%3E%3C/svg%3E")}`);
let clicked;
let openDialog = () => {
if (clicked) {
if (window.ipaInputDialog) {
window.ipaInputDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox4.js&action=raw&ctype=text/javascript');
mw.loader.using([
'jquery.textSelection', 'oojs-ui-windows', 'oojs-ui-widgets',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-editing-core',
'oojs-ui.styles.icons-editing-advanced'
]);
};
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.wikiEditor('addToToolbar', {
section: 'main',
group: 'insert',
tools: {
ipainput: {
label: 'IPAInput',
type: 'button',
oouiIcon: 'schwa',
action: { type: 'callback', execute: openDialog }
}
}
});
});
mw.hook('ve.loadModules').add(addPlugin => {
addPlugin(() => {
ve.ui.IpaInputCommand = function VeUiIpaInputCommand() {
ve.ui.IpaInputCommand.super.call(this, 'ipaInput');
};
OO.inheritClass(ve.ui.IpaInputCommand, ve.ui.Command);
ve.ui.IpaInputCommand.prototype.execute = () => {
openDialog();
return true;
};
ve.ui.commandRegistry.register(new ve.ui.IpaInputCommand());
ve.ui.IpaInputTool = function VeUiIpaInputTool() {
ve.ui.IpaInputTool.super.apply(this, arguments);
};
OO.inheritClass(ve.ui.IpaInputTool, ve.ui.Tool);
ve.ui.IpaInputTool.static.name = 'ipaInput';
ve.ui.IpaInputTool.static.group = 'insert';
ve.ui.IpaInputTool.static.icon = 'schwa';
ve.ui.IpaInputTool.static.title = 'IPA';
ve.ui.IpaInputTool.static.commandName = 'ipaInput';
ve.ui.toolFactory.register(ve.ui.IpaInputTool);
});
});
mw.requestIdleCallback(() => {
let expiry = mw.storage.get('_EXPIRY_ipainput-cache');
if (!expiry) return;
$.get(
'//en.wikipedia.org/api/rest_v1/page/title/Module%3AIPA%2Fdata'
).then(response => {
if (Date.parse(response.items[0].timestamp) / 1000 > expiry - 604800) {
mw.storage.remove('ipainput-cache');
}
});
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function wiktIpa() {
mw.loader.addStyleTag(`.oo-ui-icon-wiktionary{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath d='M14.95 1c-.15 0-.3 0-.45.03L2.9 2.9a2.26 2.26 0 0 0-1.87 2.6L2.9 17.1a2.26 2.26 0 0 0 2.6 1.86l11.6-1.88a2.26 2.26 0 0 0 1.86-2.6L17.1 2.9A2.27 2.27 0 0 0 14.95 1zm-.03.9c.63.03 1.17.49 1.28 1.14l1.88 11.6c.12.75-.37 1.43-1.12 1.56l-11.6 1.88a1.34 1.34 0 0 1-1.56-1.12L1.92 5.36A1.34 1.34 0 0 1 3.04 3.8l11.6-1.88.28-.02zm.7 2.61-2.83.46.07.39c.6-.09.95-.14 1.08.36.1.6-.91 6.53-.91 6.53s-2.87-5.16-2.98-5.87c-.02-.34.02-.64.86-.7l-.06-.4-3.64.6.07.38c.5-.15 1.01.02 1.43.82l.7 1.33-.72 4.54s-2.93-5.3-3.03-5.9c-.07-.5.45-.64.8-.66l-.06-.38-3.46.56.06.39c.24-.1.84-.07 1.07.32.07.09 4.54 8.44 4.54 8.44l.33-.05 1.02-6.24 2.98 5.59.36-.06s1.42-9.14 1.48-9.3c.05-.26.28-.71.9-.76l-.07-.39z'/%3E%3C/svg%3E")}`);
let clicked, dialog, input, $result;
let openDialog = async context => {
if (clicked) {
if (dialog) {
let selection = context.$textarea.textSelection('getSelection');
if (selection) {
input.setValue(selection);
}
if ($result) {
$result.prev().addBack().remove();
$result = null;
}
dialog.open({
actions: [{ label: 'Close', flags: ['safe', 'close'] }]
});
}
return;
}
clicked = true;
await mw.loader.using([
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.ForeignApi',
'mediawiki.util'
]);
let response = await new mw.ForeignApi('//en.wiktionary.org/w/api.php').get({
action: 'query',
generator: 'search',
gsrsearch: 'deepcat:Pronunciation_templates_by_language',
gsrnamespace: 10,
gsrlimit: 'max',
gsrsort: 'none',
formatversion: 2
});
let winMan = new OO.ui.WindowManager();
dialog = new OO.ui.MessageDialog();
winMan.addWindows([dialog]);
let items = response.query.pages
.map(p => p.title.slice(9))
.sort(Intl.Collator('en-u-kn-true').compare)
.map(s => new OO.ui.MenuOptionWidget({ label: s }));
let dropdown = new OO.ui.DropdownWidget({
$overlay: dialog.$overlay,
menu: { items }
});
let $doc = $('<p>');
dropdown.getMenu().on('choose', mw.util.debounce(async item => {
let title = 'Template:' + item.getLabel();
let $link = $('<a>').attr({
href: '//en.wiktionary.org/wiki/' + title,
target: '_blank',
title: title
}).text('documentation');
$doc.empty().append('Loading ', $link, '...');
try {
let data = await $.get(
'//en.wiktionary.org/api/rest_v1/page/html/' +
encodeURIComponent(title + '/documentation')
);
let text = $($.parseHTML(data)).find('p').first().text()
.replace(/\. .*/, '.');
$doc.text(text + ' (').append($link.text('read more'), ')');
dialog.updateSize();
} catch {
$doc.empty().append('Failed to load ', $link);
}
}, 100)).selectItem(items[0]);
input = new OO.ui.TextInputWidget({
autocomplete: false,
value: context.$textarea.textSelection('getSelection') ||
mw.config.get('wgTitle')
});
let button = new OO.ui.ButtonWidget({
disabled: !input.getValue(),
label: 'Get',
flags: ['primary', 'progressive']
}).on('click', async () => {
button.setDisabled(true);
let template = dropdown.getMenu().findSelectedItem().getLabel();
let text = input.getValue();
try {
let data = await $.post('//en.wiktionary.org/api/rest_v1/transform/wikitext/to/html', {
wikitext: `{{${template}|1=${text}}}`,
body_only: true
});
if ($result) {
$result.children().remove();
} else {
$result = $('<div>').text('Result:')
.insertAfter(fieldset.$element)
.before('<hr>');
}
$result.append($.parseHTML(data))
.find('.mw-collapsible').makeCollapsible().end()
.find('[id], [about]').removeAttr('id about').end()
.find('a').attr('target', '_blank')
.filter('[href^="./"]').attr('href', (_, href) => (
'//en.wiktionary.org/wiki' + href.slice(1)
));
dialog.updateSize();
} catch {} finally {
button.setDisabled();
}
});
input.on('change', value => {
button.setDisabled(!value);
}).connect(button, { enter: ['emit', 'click'] });
let fieldset = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(dropdown, {
label: 'Template:',
align: 'top'
}),
new OO.ui.FieldLayout(input, {
label: 'Input:',
align: 'top'
}),
new OO.ui.FieldLayout(button)
]
});
dropdown.$element.after($doc);
dialog.text.$element.append(fieldset.$element/*.on('keydown', e => {
e.stopPropagation();
})*/);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
dialog.open({
actions: [{ label: 'Close', flags: ['safe', 'close'] }]
});
};
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.wikiEditor('addToToolbar', {
section: 'main',
group: 'insert',
tools: {
wiktipa: {
label: 'WiktIPA',
type: 'button',
oouiIcon: 'wiktionary',
action: { type: 'callback', execute: openDialog }
}
}
});
});
}());
window.scripttesterSkipWarning = true;
mw.loader.using(['mediawiki.util', 'mediawiki.storage'], async function scriptTester() {
let get = () => new Set(mw.storage.getObject('scripttester'));
if (mw.storage.get('scripttester')) {
let unloading;
window.addEventListener('beforeunload', () => {
unloading = true;
});
get().forEach(async s => {
let isCss = /\.css$/i.test(s);
let url = /^(https?:)?\/\/./.test(s) ? s : mw.util.getUrl(s, {
action: 'raw',
ctype: isCss ? 'text/css' : 'text/javascript'
});
if (isCss) {
mw.loader.load(url, 'text/css');
return;
}
try {
await mw.loader.getScript(url);
} catch (e) {
if (unloading) {
console.warn(e);
return;
}
mw.notify('Failed to load temporarily installed ' + s, { type: 'error' });
}
});
} else if (!window.scripttesterSkipWarning) {
await mw.loader.using('oojs-ui-windows');
if (await OO.ui.confirm(
'You take full responsibility for any consequences arising from using ScriptTester.'
)) {
mw.storage.setObject('scripttester', []);
}
}
await $.ready;
if (!document.getElementById('p-tb')) return;
let css = mw.loader.addStyleTag('.scripttester-dialog .oo-ui-checkboxMultiselectWidget{margin:0.5em 0;word-break:break-all} .scripttester-dialog .oo-ui-flaggedElement-destructive{float:right}');
let linksShown = mw.config.get('wgNamespaceNumber') > 0 &&
mw.config.get('wgAction') === 'view';
let updateLinks = (s, unins) => {
if (!linksShown) return;
$(`.scripttester-link[data-scripttester="${s}"]`)
.toggleClass('scripttester-installed', !unins);
};
let dialog, multiselect, addButton, removeButton, clearButton;
let openDialog = () => {
if (!dialog) {
dialog = new OO.ui.MessageDialog({ classes: ['scripttester-dialog'] });
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
multiselect = new OO.ui.CheckboxMultiselectWidget().on('select', () => {
removeButton.setDisabled(!multiselect.findSelectedItems().length);
});
addButton = new OO.ui.ButtonWidget({
label: 'Add'
}).on('click', async () => {
dialog.toggle(false);
let s = (await OO.ui.prompt('Add a script', {
textInput: { placeholder: 'Script page name or URL' }
})).trim();
if (!s) return;
if (!/^(https?:)?\/\/./.test(s) && !mw.Title.newFromText(s)) {
await OO.ui.alert(`"${s}" does not appear to be a valid page name or URL.`);
dialog.toggle(true);
updateDialog();
return;
}
mw.storage.setObject('scripttester', [...get().add(s)]);
updateLinks(s);
});
removeButton = new OO.ui.ButtonWidget({
label: 'Remove'
}).on('click', () => {
let set = get();
multiselect.findSelectedItems().forEach(item => {
let s = item.getLabel();
set.delete(s);
updateLinks(s, true);
});
mw.storage.setObject('scripttester', [...set]);
updateDialog();
});
clearButton = new OO.ui.ButtonWidget({
label: 'Clear',
flags: 'destructive'
}).on('click', async () => {
dialog.toggle(false);
if (!(await OO.ui.confirm('Uninstall all scripts?'))) {
dialog.toggle(true);
return;
}
mw.storage.setObject('scripttester', []);
if (linksShown) {
$('.scripttester-installed').removeClass('scripttester-installed');
}
dialog.toggle(true);
updateDialog();
});
dialog.text.$element.append(
multiselect.$element,
new OO.ui.ButtonGroupWidget({
items: [addButton, removeButton]
}).$element,
clearButton.$element
);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
}
updateDialog();
dialog.open({
message: 'Temporarily installed scripts:',
actions: [{ label: 'Done', flags: ['safe', 'close'] }]
});
};
let updateDialog = () => {
let set = get();
multiselect.clearItems().addItems(
[...set].map(s => new OO.ui.CheckboxMultioptionWidget({ label: s }))
);
removeButton.setDisabled(true);
clearButton.toggle(set.size);
dialog.updateSize();
updatePortlet(set.size);
};
let updatePortlet = count => {
$(portletLink).find('*').addBack().contents().each(function () {
if (this.nodeType === 3 && this.textContent.trim()) {
this.textContent = `Temporarily installed scripts (${count})`;
return false;
}
});
};
let portletLink = mw.util.addPortletLink('p-tb', '#', `Temporarily installed scripts (${get().size})`)
.firstElementChild;
portletLink.addEventListener('click', e => {
e.preventDefault();
mw.loader.using(['oojs-ui-windows', 'mediawiki.Title'], openDialog);
});
window.addEventListener('storage', e => {
if (e.key === 'scripttester') {
updatePortlet(get().size);
}
});
if (!linksShown) return;
css.textContent += ' .scripttester{font-size:85%;user-select:none} .scripttester::before{content:" "} .scripttester-link::after{content:"[+]"} .scripttester-installed::after{content:"[−]"} #firstHeading > .scripttester{font-size:47%}';
let linkHandler = function () {
let s = this.dataset.scripttester;
let unins = this.classList.contains('scripttester-installed');
let set = get();
set[unins ? 'delete' : 'add'](s);
let success = mw.storage.setObject('scripttester', [...set]);
if (success) {
mw.notify((unins ? 'Uninstalled ' : 'Installed ') + s, {
tag: 'scripttester'
});
updateLinks(s, unins);
updatePortlet(set.size);
} else {
mw.notify(`Couldn't ${unins ? 'un' : ''}install ${s}`, {
tag: 'scripttester',
type: 'error'
});
}
};
if ([2, 4, 8].includes(mw.config.get('wgNamespaceNumber')) &&
['javascript', 'css'].includes(mw.config.get('wgPageContentModel'))
) {
let s = mw.config.get('wgPageName').replaceAll('_', ' ');
$('<span>').addClass('scripttester').append(
$('<a>').attr({
class: 'scripttester-link' + (get().has(s) ? ' scripttester-installed' : ''),
tabindex: 0,
role: 'button',
'data-scripttester': s
}).text('\u200e').on('click', linkHandler)
).appendTo(document.getElementById('firstHeading'));
return;
}
mw.hook('wikipage.content').add($content => {
let set = get();
let ns = mw.config.get('wgFormattedNamespaces');
let re = new RegExp(`^(${ns[2]}|${ns[4]}|${ns[8]}):.+\\.([Cc][Ss]|[Jj])[Ss]$`);
$content.find('a:not(.external, .new)').after(function () {
let s = this.title;
if (!s || !re.test(s)) return;
return $('<span>').addClass('scripttester').append(
$('<a>').attr({
class: 'scripttester-link' + (set.has(s) ? ' scripttester-installed' : ''),
tabindex: 0,
role: 'button',
'data-scripttester': s
}).text('\u200e').on('click', linkHandler)
);
});
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.hook('wikiEditor.toolbarReady').add($textarea => {
$textarea.on('keydown', async e => {
if (e.which !== 72 || e.shiftKey || e.altKey || [e.ctrlKey, e.metaKey].filter(Boolean).length !== 1) return;
e.preventDefault();
let context = $textarea.data('wikiEditorContext');
context.api.openDialog(context, 'search-and-replace');
await mw.loader.using('jquery.textSelection');
let tb = document.getElementById('wikieditor-toolbar-replace-search');
let sel = $textarea.textSelection('getSelection');
if (sel) {
tb.value = sel;
}
tb.focus();
});
$(document.body).on('dialogclose', '#wikieditor-toolbar-replace-dialog', () => {
$textarea[0].focus();
});
});
mw.config.get('wgNamespaceNumber') &&
mw.config.get('wgAction') !== 'history' &&
(function catChangeHighlighter() {
let run;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-changeslist-line.mw-changeslist-src-mw-categorize').each(function () {
let text = this.querySelector('.comment').textContent;
if (text.includes(' added ')) {
this.classList.add('catchangehighlighter-addition');
} else if (text.includes(' removed ')) {
this.classList.add('catchangehighlighter-removal');
}
if (run) return;
run = true;
mw.loader.addStyleTag('.catchangehighlighter-addition :is(span, td) > .comment{background:#f5fff5} .catchangehighlighter-removal :is(span, td) > .comment{background:#fff5f5}');
});
});
}());
(mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') !== 'view' ||
[-1, 14].includes(mw.config.get('wgNamespaceNumber'))) &&
(function diffFontSwitcher() {
mw.loader.addStyleTag('.diff-lineno{cursor:pointer}');
$(document.body).on('click keydown', '.diff-lineno', function (e) {
if (e.type === 'keydown' && (
e.which !== 13 && e.which !== 32 ||
e.ctrlKey || e.shiftKey || e.metaKey || e.altKey
)) {
return;
}
e.preventDefault();
this.closest('.diff').classList.toggle('difffontswitcher-enabled');
});
mw.hook('wikipage.diff').add($diff => {
$diff.find('.diff-lineno').attr({ tabindex: 0, role: 'button' });
});
}());
mw.trackSubscribe('resourceloader.exception', (topic, data) => {
mw.notify(data.exception, {
autoHide: false,
title: `Exception in ${data.source} in module ${data.module}`,
type: 'warn'
});
});
mw.config.get('skin') === 'vector-2022' &&
$(document).one('click', '.mw-interlanguage-selector', async () => {
await mw.loader.using('ext.uls.mediawiki');
$.fn.uls.Constructor.prototype.getMenuWidth = () => 'narrow';
mw.uls.getFrequentLanguageList = () => [];
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/DiffUndo.js&action=raw&ctype=text/javascript', 's');
cj67pnwa1ibeuqy2pio9hv3mbe1u7ny
User:Nardog/sandbox2.js
2
118608
735544
735446
2026-03-29T13:45:50Z
Nardog
40946
735544
javascript
text/javascript
(async function listTools() {
let pageAction = mw.config.get('wgAction');
let isView = pageAction === 'view';
let isEdit = ['edit', 'submit'].includes(pageAction);
if (!isView && !isEdit) return;
let pageType = mw.config.get('wgCanonicalSpecialPageName') ||
mw.config.get('wgNamespaceNumber');
if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') &&
!mw.config.get('wgIsRedirect') &&
!mw.config.get('wgPageName').includes('/')
) {
return;
}
await mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api',
'mediawiki.interface.helpers.styles'
]);
mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) {
font-size: 85%;
}
.listtools, .listtools a {
font-weight: normal !important;
font-style: normal;
}
.mw-datatable .listtools {
display: block;
}
.listtools + .mw-whatlinkshere-tools,
#watchlist-edit-form .listtools ~ .mw-changeslist-links,
.mw-special-DisambiguationPageLinks .listtools + a {
display: none;
}`);
let messages = Object.assign({
watched: 'Added "$1" to your watchlist',
watchFail: `Couldn't watch "$1"`,
unwatchFail: `Couldn't unwatch "$1"`
}, window.listtoolsMessages);
let getMsg = (key, ...args) => (
Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key
);
let notif;
let watchHandler = async function (e) {
e.preventDefault();
let $link = $(this);
let $wrapper = $link.parent();
$link.detach();
let params = new URLSearchParams(this.search);
let action = params.get('action');
$wrapper.text(getMsg(action + 'ing'));
let pn = params.get('title').replaceAll('_', ' ');
let promise = new mw.Api()[action](pn);
if (notif) {
notif.close();
notif = null;
}
try {
let result = await promise;
if (!result || !result[action + 'ed']) throw '';
let newAction = action === 'watch' ? 'unwatch' : 'watch';
params.set('action', newAction);
$link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`)
.attr('href', this.pathname + '?' + params)
.text(getMsg(newAction));
if (action !== 'watch') return;
let require = await mw.loader.using([
'mediawiki.notification', 'mediawiki.watchstar.widgets'
]);
notif = await mw.notify(
new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, {
message: getMsg('watched', pn)
}).$element,
{ tag: 'listtools' }
);
} catch {
notif = await mw.notify(getMsg(action + 'Fail', pn), {
tag: 'listtools',
type: 'error'
});
} finally {
$wrapper.html($link);
}
};
let extGetMain = function () {
return this.title;
};
let re = new RegExp(`(?:\\?title=|${
mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), ''))
})([^#&?]+)`);
let processed = new WeakSet();
let processLinks = ($links, module, titles) => {
let isBatch = !!titles;
titles = titles || new Set();
$links.each(function (i) {
if (processed.has(this)) return;
let $link = $links.eq(i);
let pn;
if (module.useText) {
pn = $link.text();
} else {
let match = $link.attr('href')?.match(re);
if (!match) return;
pn = decodeURIComponent(match[1]);
}
let t = mw.Title.newFromText(pn);
if (!t) return;
if (module.titlesOnly) {
let text = $link.text();
if (text !== pn.replaceAll('_', ' ') &&
(text !== t.getMainText() || t.namespace === 2)
) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.namespace < 0) return;
if ($link.hasClass('new')) {
t.missing = true;
}
titles.add(t.getSubjectPage().toText());
}
let $tools = $('<span>').addClass('listtools mw-changeslist-links')
.data('listtools', t);
tools.forEach(tool => {
addTool($tools, tool);
});
if ($link.is(':is(del, bdi) > :only-child')) {
if (module.position === 'end') {
$link.parent().parent().append(' ', $tools);
} else {
$link.parent().after(' ', $tools);
}
} else if (module.position === 'end') {
$link.parent().append(' ', $tools);
} else {
$link.after(' ', $tools);
}
if (module.post) {
module.post($tools);
}
processed.add(this);
});
if (!isBatch) {
getWatched(titles);
}
};
let tools = [
{
name: 'edit',
url: t => t.getUrl({ action: 'edit' })
},
{
name: 'hist',
url: t => !t.missing && t.getUrl({ action: 'history' })
},
{
name: 'links',
url: t => mw.util.getUrl('Special:WhatLinksHere/' + t)
},
{
name: 'watch',
url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }),
callback: watchHandler
}
];
let addTool = ($tools, tool, escapedName) => {
let t = $tools.data('listtools');
let $duplicate = escapedName &&
$tools.children('.listtools-' + escapedName);
let url = tool.url;
if (typeof url === 'function') {
url = url(t);
if (!url) {
$duplicate?.remove();
return;
}
}
let $link = $('<a>').attr('href', url).text(getMsg(tool.name));
if (t.host) {
$link.prop('host', t.host);
}
if (tool.callback) {
$link.on('click', tool.callback);
}
let $wrapper = $('<span>').addClass('listtools-' + tool.name)
.append($link);
let $next = tool.next && $tools.children('.listtools-' + tool.next);
if ($next?.length) {
$duplicate?.remove();
$next.before($wrapper);
} else if ($duplicate?.length) {
$duplicate.replaceWith($wrapper);
} else {
$tools.append($wrapper);
}
};
let extend = tool => {
if (tool.label && !Object.hasOwn(messages, tool.label)) {
messages[tool.name] = tool.label;
}
if (tool.next) {
tool.next = $.escapeSelector(tool.next);
}
let existingTool = tools.find(t => t.name === tool.name);
if (existingTool) {
Object.assign(existingTool, tool);
} else {
tools.push(tool);
}
let escapedName = existingTool && $.escapeSelector(tool.name);
let $allTools = $('.listtools');
$allTools.each(function (i) {
addTool($allTools.eq(i), tool, escapedName);
});
};
let getWatched = async titles => {
if (!Array.isArray(titles)) {
titles = [...titles].slice(0, 500);
}
if (!titles.length) return;
(await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages.forEach(page => {
if (!page.watched) return;
$(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`)
.attr('href', mw.util.getUrl(page.title, { action: 'unwatch' }))
.text(getMsg('unwatch'));
});
getWatched(titles.slice(50));
};
mw.hook('listtools.ready').fire(extend);
let catTreeCallback = (records, observer) => {
let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a');
if ($links.length) {
observer.takeRecords();
observer.disconnect();
processLinks($links, catTreeModule);
}
};
let catTreeModule = {
selector: '.CategoryTreeItem > bdi > a',
types: [14, 'CategoryTree'],
position: 'end',
post: $tools => {
$tools.parent().next('.CategoryTreeChildren').each(function () {
new MutationObserver(catTreeCallback)
.observe(this, { childList: true });
});
}
};
let modules = [
{
selector: '#mw-pages li > a, #mw-pages li > span > a',
types: [14]
},
catTreeModule,
{
selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a',
types: [6]
},
{
selector: '#mw-globalusage-result a',
types: ['GlobalUsage']
},
{
selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a',
types: ['Search']
},
{
selector: '.mw-search-createlink a',
types: ['Search'],
titlesOnly: true
},
{
selector: '#watchlist-edit-form .cdx-table td > label > a',
types: ['EditWatchlist']
},
{
selector: '.plainlinks > li > a',
types: ['AbuseLog'],
titlesOnly: true
},
{
selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)',
types: ['Allmessages'],
position: 'end'
},
{
selector: '.mw-spcontent li a',
types: ['DisambiguationPageLinks', 'Listredirects'],
titlesOnly: true
},
{
selector: 'li > a:first-child',
types: ['FileDuplicateSearch']
},
{
selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a',
types: ['LintErrors'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: 'form > ul > li > a',
types: ['Nuke'],
position: 'end',
titlesOnly: true
},
{
selector: '.page-assessments a',
types: ['PageAssessments'],
titlesOnly: true
},
{
selector: '.TablePager_col_pr_page > a',
types: ['Protectedpages'],
position: 'end'
},
{
selector: '#mw-content-text > ul a',
types: ['Protectedtitles'],
position: 'end'
},
{
selector: '.mw-fr-pending-changes-page-title',
types: ['PendingChanges'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: '#mw-content-text > ul a:first-child',
types: ['StablePages'],
position: 'end'
},
{
selector: '.TablePager_col__page a',
types: ['TopicSubscriptions']
},
{
selector: '.undeleteResult > a',
types: ['Undelete'],
position: 'end',
useText: true
},
{
selector: '.TablePager_col_img_name > a:first-child',
// types: ['Listfiles'],
position: 'end'
},
{
selector: '.mw-newpages-pagename',
post: $tools => {
let $contents = $tools.parent().contents();
$contents.slice(
$contents.index($tools) + 1,
$contents.index($contents.filter('.mw-newpages-length'))
).replaceWith(' ');
}
},
{
selector: '#mw-whatlinkshere-list li > bdi > a'
},
{
selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)',
titlesOnly: true
},
{
selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a',
types: ['Log'],
titlesOnly: true
},
{
selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a',
types: ['ComparePages'],
position: 'end'
},
{
selector: '#movepage-oldlink, #movepage-newlink',
types: ['Movepage']
},
{
selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)',
types: ['Undelete'],
useText: true
},
{
selector: '.galleryfilename, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
}());
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// }).then(response => {
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// }).always(() => {
// $wrapper.html($link);
// });
// }
// });
extend({
name: 'copy',
url: '#',
callback: function (e) {
e.preventDefault();
let text = $(this).closest('.listtools').data('listtools').toText();
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch (err) {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
}
});
});
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' &&
(function consecudiff() {
mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}');
let isHist = mw.config.get('wgAction') === 'history';
class Consecudiff {
constructor(lis, isContribs) {
this.isContribs = isContribs;
this.isEnhanced = !isHist && !isContribs &&
lis[0].classList.contains('mw-enhanced-rc');
this.threshold = isContribs ? window.consecudiffContribsThreshold || 120
: isHist ? window.consecudiffHistThreshold || 720
: window.consecudiffThreshold || 720;
this.strictMode = !isContribs &&
!!window.consecudiffDetectInterruptions;
this.diffSelector = isHist
? 'a.mw-history-histlinks-previous'
: '.mw-changeslist-diff';
this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' ||
(isHist || isContribs) && 'a.mw-changeslist-date';
this.hybridSelector = this.diffSelector;
if (this.permaSelector) {
this.hybridSelector += ', ' + this.permaSelector;
}
this.topClass = isContribs
? 'mw-contributions-current'
: 'mw-changeslist-last';
let dependencies = ['mediawiki.util'];
if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') {
dependencies.push('mediawiki.language.months');
}
mw.loader.using(dependencies, () => {
let chunks;
if (isHist) {
chunks = this.chunkByUser(lis);
} else {
chunks = [];
this.groupByTitle(lis).forEach(group => {
chunks.push(...this.chunkByUser(group));
});
}
let subchunks = [];
chunks.forEach(chunk => {
subchunks.push(...this.divideByDate(chunk));
});
let linkPairs = [];
subchunks.forEach(subchunk => {
linkPairs.push(...this.makeLinks(subchunk));
});
linkPairs.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
});
}
groupByTitle(lis) {
let selector = this.isContribs
? '.mw-contributions-title'
: '.mw-changeslist-title';
let lisByTitle = {};
lis.forEach(li => {
let link = (this.isEnhanced ? li.closest('table') : li)
.querySelector(selector);
if (!link) return;
let title = link.textContent;
if (!lisByTitle.hasOwnProperty(title)) {
lisByTitle[title] = [];
}
lisByTitle[title].push(li);
});
return Object.values(lisByTitle).filter(group => group.length > 1);
}
chunkByUser(lis) {
if (this.isSingleContribs) {
return [lis];
}
let chunks = [], lastSplitAt = 0, prevUser;
this.isSingleContribs = lis.some((li, i) => {
let link = li.querySelector('.mw-userlink');
if (!link && this.isContribs) {
return true;
}
let user = link && link.textContent;
if (!link || i && user !== prevUser) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevUser = user;
});
if (this.isSingleContribs) {
return [lis];
}
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
divideByDate(lis) {
let chunks = [], lastSplitAt = 0, prevDate;
lis.forEach((li, i) => {
let date;
if (isHist || this.isContribs) {
date = this.parseDate(
li.querySelector('.mw-changeslist-date').textContent
);
} else {
date = Date.parse(
li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z')
);
}
if (date) {
date = date / 60000;
}
if (i && prevDate - date > this.threshold) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevDate = date;
if (!this.strictMode || lastSplitAt === i) return;
let prevDiff = lis[i - 1].querySelector(this.diffSelector);
if (prevDiff) {
let prevNext = mw.util.getParamValue('oldid', prevDiff.search);
if (prevNext !== li.dataset.mwRevid) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
}
});
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
makeLinks(lis) {
let count = lis.length;
let firstPerma;
let start = lis.findIndex(li => (
firstPerma = li.querySelector(this.hybridSelector)
));
if (start === -1 || count - start < 2) return [];
let end, lastDiff;
for (let i = count - 1; i > start; i--) {
if (!isHist && !this.isContribs) {
lastDiff = lis[i].querySelector(this.diffSelector);
if (lastDiff ||
lis[i].classList.contains('mw-changeslist-src-mw-new')
) {
end = i + 1;
break;
}
}
if (this.permaSelector && lis[i].querySelector(this.permaSelector)) {
end = i + 1;
break;
}
}
if (!end) return [];
count = end - start;
let params = { diff: lis[start].dataset.mwRevid };
if (lastDiff) {
params.oldid = mw.util.getParamValue('oldid', lastDiff.search);
} else {
params.oldid = lis[end - 1].dataset.mwRevid;
if (isHist && lis[end - 1].querySelector(this.diffSelector) ||
this.isContribs && !lis[end - 1].querySelector('.newpage')
) {
params.direction = 'prev';
}
}
let title = !isHist && mw.util.getParamValue('title', firstPerma.search);
let url = mw.util.getUrl(title, params);
let classes = 'consecudiff';
if (!isHist && lis[start].classList.contains(this.topClass)) {
classes += ' consecudiff-top';
}
return lis.slice(start, end).map((li, i) => [
$('<span>').addClass(classes).append(
$('<a>')
.attr('href', url)
.text(this.convertNumber(count - i + '/' + count))
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
parseDate(s) {
let date = Date.parse(s);
if (date) {
return date;
}
if (s.includes(',')) date = Date.parse(s.replace(',', ''));
if (date) {
return date;
}
if (mw.loader.getState('mediawiki.language.months') !== 'ready') return;
s = s.replace(/\D/g, c => {
let n = mw.language.convertNumber(c, true);
return Number.isNaN(n) ? c : n;
});
let h, m;
s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => {
h = $1;
m = $2;
return ' ';
});
if (!h) return;
let y, dateFirst;
s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => {
y = $2;
dateFirst = /\d/.test($1);
return $1 + ' ';
});
if (!y) return;
let mo, d;
if (dateFirst) {
[d, s] = this.getDate(s);
if (!d) return;
[mo, s] = this.getMonth(s);
if (mo === -1) return;
} else {
[mo, s] = this.getMonth(s);
if (mo === -1) return;
[d, s] = this.getDate(s);
if (!d) return;
}
return new Date(y, mo, d, h, m).getTime();
}
getMonth(s) {
if (!this.months) {
this.months = mw.language.months.abbrev
.concat(mw.language.months.names, mw.language.months.genitive)
.reverse();
}
let mo = this.months.findIndex(mn => {
let temp = s.replace(mn, ' ');
if (temp !== s) {
s = temp;
return true;
}
});
if (mo === -1) {
let [numeric, temp] = this.getDate(s);
numeric = parseInt(numeric);
if (numeric > 0 && numeric < 13) {
mo = numeric - 1;
s = temp;
}
} else {
mo = 11 - mo % 12;
}
return [mo, s];
}
getDate(s) {
let d;
s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => {
d = $2;
return $1 + ' ';
});
return [d, s];
}
convertNumber(num) {
try {
return mw.language.convertNumber(num);
} catch (e) {
return num;
}
}
}
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-body').each(function () {
let lis = this.querySelectorAll('.mw-contributions-list > li');
if (lis.length > 1) {
new Consecudiff([...lis], !isHist);
}
});
if (isHist) return;
let $lists = $content.filter('.mw-changeslist');
if (!$lists.length) {
$lists = $content.find('.mw-changeslist');
}
$lists.each(function () {
let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]');
if (lis.length > 1) {
new Consecudiff([...lis]);
}
});
});
}());
if (mw.config.get('wgNamespaceNumber') === 14 && (
mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId')
)) {
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter',
'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement',
'mediawiki.interface.helpers.styles', 'user.options'
]);
}
$(function moveHistory() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.moveHistoryDialog) {
window.moveHistoryDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter',
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions',
'mediawiki.interface.helpers.styles'
]);
});
});
});
$(function sectionSearch() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.sectionSearchDialog) {
window.sectionSearchDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows',
'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget'
]);
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' &&
mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() {
mw.hook('wikipage.content').add($content => {
let $table = $content.find('.mw-centralauth-wikislist').has('td');
if (!$table.length) return;
$table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
[10, 828].includes(mw.config.get('wgNamespaceNumber')) &&
!mw.config.get('wgTitle').endsWith('/doc') &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// $(function templatePreviewGuard() {
// let button = document.querySelector('input[name="wpTemplateSandboxPreview"]');
// if (!button) return;
// let proceed;
// button.addEventListener('click', e => {
// if (proceed) {
// proceed = false;
// return;
// }
// e.preventDefault();
// e.stopPropagation();
// let formData = new FormData(button.form);
// let page = formData.get('wpTemplateSandboxPage');
// let temp = formData.get('wpTemplateSandboxTemplate');
// if (!page || !temp) return;
// mw.loader.using('mediawiki.api').then(() => (
// new mw.Api().get({
// action: 'query',
// titles: page,
// prop: 'templates',
// tltemplates: temp,
// formatversion: 2
// })
// )).always(response => {
// if (((((response || {}).query || {}).pages || [])[0] || {}).templates ||
// confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`)
// ) {
// proceed = true;
// button.click();
// }
// });
// }, true);
// if (!mw.config.get('wgArticleId')) return;
// let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget');
// if (!widgetEl) return;
// let pn = mw.config.get('wgPageName').replace(/_/g, ' ');
// mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => (
// new mw.Api().get({
// action: 'query',
// titles: pn,
// prop: 'transcludedin',
// tiprop: 'title',
// tilimit: 'max',
// formatversion: 2
// })
// )).then(response => {
// if (!response.batchcomplete) return;
// let pages = response.query.pages[0].transcludedin
// .filter(o => o.title !== pn);
// if (!pages.length) return;
// let widget = OO.ui.infuse(widgetEl);
// if (pages.length === 1) {
// widget.setValue(pages[0].title);
// return;
// }
// widget.$element.replaceWith(
// new OO.ui.ComboBoxInputWidget({
// id: 'wpTemplateSandboxPage',
// maxlength: widget.$input.prop('maxLength'),
// name: widget.$input.prop('name'),
// options: pages
// .sort((a, b) => a.ns - b.ns || -(a.title < b.title))
// .map(o => ({ data: o.title })),
// placeholder: widget.$input.prop('placeholder'),
// tabIndex: widget.getTabIndex(),
// value: widget.getValue()
// }).on('enter', e => {
// e.preventDefault();
// button.click();
// }).$element
// );
// });
// });
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoSectionLink.js&action=raw&ctype=text/javascript', 's');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(function copyRevId() {
let handler = function (e) {
e.preventDefault();
let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid ||
this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!text) return;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
document.execCommand('copy');
$input.remove();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id')
)
);
});
}());
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
let handler = async function (e) {
e.preventDefault();
let td = this.closest('.diff td');
let rev = td
? td.querySelector('[data-mw-revid]')?.dataset.mwRevid
: this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!rev) {
mw.notify(`Couldn't get the revision.`, {
tag: 'markasunseen',
type: 'error'
});
return;
}
let pn = td
? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title')
: mw.config.get('wgPageName');
if (!pn) return;
await mw.loader.using('mediawiki.api');
let result = (await new mw.Api().postWithEditToken({
action: 'setnotificationtimestamp',
[td ? 'newerthanrevid' : 'torevid']: rev,
titles: pn,
formatversion: 2
})).setnotificationtimestamp?.[0];
if (Object.hasOwn(result, 'notificationtimestamp')) {
mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'success'
});
} else if (result?.notwatched) {
mw.notify('This page is not on your watchlist.', {
tag: 'markasunseen',
type: 'warn'
});
} else {
mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'error'
});
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions after this one as unseen'
}).on('click', handler).text('unseen'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions since this one as unseen'
}).on('click', handler).text('unseen')
)
);
});
})();
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) ||
(mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) &&
mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() {
let handler = function (e) {
e.preventDefault();
let parent = this.closest('li, td');
let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0];
if (!ts) return;
let user = parent.querySelector('.mw-userlink').textContent;
if (mw.util.isIPv6Address(user)) {
user = user.toUpperCase();
}
let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned';
let text = `{{subst:${temp}|${user}|${ts}}}`;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () {
if (mw.config.get('wgWikiID') === 'metawiki') {
return true;
}
let link = this.querySelector('strong > a') ||
this.parentElement.querySelector('#differences-prevlink, #differences-nextlink');
if (!link) return;
let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search));
return t.isTalkPage() || t.namespace === 4;
}).append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig')
)
);
});
});
// mw.config.get('wgAction') === 'history' &&
// mw.loader.using('mediawiki.util', function () {
// mw.hook('wikipage.content').add($content => {
// $content.find('a.mw-changeslist-date').after(function () {
// return [
// ' (',
// $('<a>').attr('href', mw.util.getUrl(null, {
// action: 'edit',
// oldid: this.closest('li').dataset.mwRevid
// })).text('e'),
// ')'
// ];
// });
// });
// });
// ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
// mw.hook('wikipage.content').add($content => {
// $content.find('.mw-changeslist-history').parent().after(function () {
// return $('<span>').append(
// $('<a>').attr(
// 'href',
// this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit'
// ).text('e')
// );
// });
// });
if (screen.width < 500) {
mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype);@font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)}} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}');
location.hash && $(() => {
let target = document.querySelector(':target');
if (target?.getBoundingClientRect().top < 0) {
target.scrollIntoView();
}
});
}
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(mw.config.exists('wgCodeEditorCurrentLanguage') ||
mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') &&
(function saveNEdit() {
let notif;
$(document.body).on('click', '#wpSave', async function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
e.originalEvent?.defaultPrevented
) {
return;
}
e.preventDefault();
await mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core'
]);
let button = OO.ui.infuse(this.parentElement).setDisabled(true);
let $textarea = $('#wpTextbox1');
let text = $textarea.textSelection('getContents');
let $summary = $('#wpSummary');
let formData = new FormData(this.form);
let promise = new mw.Api().postWithEditToken({
action: 'edit',
title: mw.config.get('wgPageName'),
text: text,
section: formData.get('wpSection') || undefined,
summary: $summary.textSelection('getContents'),
[$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1,
baserevid: formData.get('editRevId'),
basetimestamp: formData.get('wpEdittime'),
starttimestamp: formData.get('wpStarttime'),
watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch',
watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined,
undo: formData.get('wpUndidRevision') || undefined,
undoafter: formData.get('wpUndoAfter') || undefined,
contentformat: formData.get('format'),
contentmodel: formData.get('model'),
assertuser: mw.config.get('wgUserName'),
formatversion: 2
});
notif?.close();
notif = null;
try {
let response = await promise;
if (response?.edit?.result !== 'Success') throw '';
$('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove();
$textarea.data('origtext', text).prop('defaultValue', text);
$summary.val($summary.prop('defaultValue'));
if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') {
let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js'];
storage.deleteData(mw.config.get('wgPageName'));
storage.closeDatabase();
}
notif = await mw.notify(response.edit.nochange ? 'No change' : [
document.createTextNode('Saved'),
$('<p>').append(
new OO.ui.ButtonWidget({
href: mw.util.getUrl(),
target: '_blank',
label: 'View'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, {
diff: response.edit.newrevid || 'cur',
diffonly: 1
}),
target: '_blank',
label: 'Diff'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, { action: 'history' }),
target: '_blank',
label: 'History'
}).$element
)[0]
], { tag: 'savenedit' });
} catch (error) {
notif = await mw.notify(error?.error?.info || error || 'Save failed', {
autoHideSeconds: 'long',
tag: 'savenedit',
type: 'error'
});
} finally {
button.setDisabled();
}
});
}());
mw.config.get('wgNamespaceNumber') === 0 &&
mw.config.get('wgAction') === 'view' &&
mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) &&
$(() => {
let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, ''));
let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`);
if (!$links.length) return;
let titles = {};
$links = $links.filter(function () {
let text = this.textContent;
return !(titles[text] = Object.hasOwn(titles, text));
});
mw.notify(
$links.length === 1
? $links.clone()
: $('<ul>').append($links.clone().wrap('<li>').parent()),
{ autoHideSeconds: 'long' }
);
});
['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
$.when($.ready, mw.loader.using([
'user.options', 'mediawiki.util', 'mediawiki.api'
])).then(function rcMuter() {
let os = mw.user.options.get('userjs-rcmuter');
let set = new Set(os && os.split('|'));
let save = () => {
let ns = [...set].join('|');
if (ns === mw.user.options.get('userjs-rcmuter')) return;
new mw.Api().saveOption('userjs-rcmuter', ns);
mw.user.options.set('userjs-rcmuter', ns);
$edit.attr('data-rcmuter', set.size);
};
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
'data-rcmuter': set.size
}).text('Edit muted').on('click', e => {
e.preventDefault();
mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]).then(() => OO.ui.getWindowManager().getWindow('message')).then(dialog => {
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...set]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
let instance = dialog.open({
message: $([
document.createTextNode('Muted users:'),
multiselect.$element[0]
]),
size: 'medium'
});
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().menu.toggle(false);
});
});
instance.closed.then(result => {
if (!result || result.action !== 'accept') return;
set = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
});
let buttonsShown;
let $toggleButtons = $('<a>').attr('href', '#').text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (buttonsShown) {
mw.hook('wikipage.content').remove(addButtons);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addButtons);
this.textContent = 'Hide toggle buttons';
}
buttonsShown = !buttonsShown;
});
let $toggle = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleSpan = $('<span>').hide().append($toggle);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleButtons),
$toggleSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-userlink ~ .mw-usertoollinks, .mw-changeslist-line-inner-userLink ~ .mw-changeslist-line-inner-userTalkLink')
.prevAll('.mw-userlink, .mw-changeslist-line-inner-userLink')
.last().text().trim();
if (!user) {
mw.notify(`Can't retrieve the username.`, {
tag: 'rcmuter',
type: 'error'
});
return;
}
let muting = this.parentElement.classList.toggle('rcmuter-unmute');
set[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addButtons = $content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) {
$content = $('#mw-content-text');
if ($content.has('.rcmuter-toggle').length) return;
}
let $tools = $content.find('.mw-usertoollinks.mw-changeslist-links');
let $muted = $tools.filter('.rcmuter-muted *');
$tools.not($muted).append(
$('<span>').addClass('rcmuter-toggle').append(
$('<a>').attr('href', '#').text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr('href', '#').text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let muted = set.has(this.textContent);
if (muted) mutedCount++;
return muted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!set.size) {
$toggleSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-userlink:only-child')
.filter(filter).closest('table').addClass('rcmuter-muted');
$content.find('.mw-userlink:not(.changedby > *, .comment *, .rcmuter-muted *)')
.filter(filter).closest('.mw-changeslist-line, table').addClass('rcmuter-muted')
.closest('table.mw-enhanced-rc').find('.changedby > .mw-userlink').filter(filter).addClass('rcmuter-muted');
$toggleSpan.toggle(!!mutedCount);
$toggle.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() {
if (!document.getElementById('p-tb')) return;
let messages = Object.assign({
portlet: 'RefRenamer',
loading: 'Loading RefRenamer...'
}, window.refrenamerMessages);
let clicked;
mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.refRenamer) {
window.refRenamer();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
if (['edit', 'submit'].includes(mw.config.get('wgAction'))) {
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's');
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's');
}
mw.config.get('wgAction') !== 'history' &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's');
mw.config.exists('wgDiffNewId') &&
mw.config.get('wgDiscussionToolsFeaturesEnabled') &&
(function () {
let data = {}, clickHandler, autoClear, run;
window.dtc = data;
let highlight = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js']
.highlightNewComments(mw.dt.pageThreads, true, ids);
if (clickHandler) {
$(document.body).off('click', clickHandler);
return;
}
$._data(document.body, 'events').click.some(o => {
if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) {
$(document.body).off('click', o.handler);
clickHandler = o.handler;
return true;
}
});
$._data(window, 'events').popstate.some(o => {
if (String(o.handler).includes('highlighter.highlightTargetComment(')) {
$(window).off('popstate', o.handler);
return true;
}
});
};
let scroll = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
let yToSpan = Object.fromEntries(
ids.map(id => document.getElementById(id)).filter(Boolean)
.map(span => [span.getBoundingClientRect().y, span])
);
let ys = Object.keys(yToSpan);
if (!ys.length) return;
let lower = ys.filter(y => y > 10);
if (!lower.length ||
Math.max(...lower) < document.documentElement.clientHeight
) {
yToSpan[Math.min(...ys)].scrollIntoView();
} else {
yToSpan[Math.min(...lower)].scrollIntoView();
}
};
let scrollToNext = function (e) {
e.preventDefault();
let revId = mw.config.get('wgDiffOldId');
if (!revId || !data[revId]) return;
let i = data[revId].indexOf(
this.closest('[data-mw-thread-id]').dataset.mwThreadId
);
if (i === -1) return;
let next = data[revId][i + 1] || data[revId][0];
document.getElementById(next).scrollIntoView();
};
mw.hook('wikipage.content').add(async $content => {
let revId = mw.config.get('wgDiffOldId');
if (!revId) return;
let param = new URLSearchParams(location.search).get('diffonly');
if (param && param !== '0') return;
if (data[revId]) {
highlight(revId);
return;
}
await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']);
let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp'));
data[revId] = mw.dt.pageThreads.getCommentItems()
.filter(c => c.timestamp > begin).map(c => c.id);
if (!data[revId].length) return;
await new Promise(setTimeout);
highlight(revId);
$content.find('.ext-discussiontools-init-replylink-buttons').filter(function () {
return data[revId].includes(this.dataset.mwThreadId);
}).children('span:last-of-type').before(
' | ',
$('<a>').attr({
href: '#',
role: 'button'
}).text('next').on('click', scrollToNext)
);
if (run || !document.getElementById('p-tb')) return;
run = true;
let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext');
portlet.firstElementChild.addEventListener('click', e => {
e.preventDefault();
scroll(mw.config.get('wgDiffOldId'));
});
mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => {
e.preventDefault();
autoClear = !autoClear;
if (autoClear) {
$(document.body).on('click', clickHandler)[0].click();
} else {
highlight(mw.config.get('wgDiffOldId'));
}
});
mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`);
});
}());
mw.config.get('wgNamespaceNumber') === 6 &&
mw.config.get('wgAction') === 'view' &&
mw.hook('wikipage.content').add($content => {
$content.find('.filehistory .mw-usertoollinks-contribs').after(function () {
return [
' | ',
$('<a>').attr('href', `${
mw.config.get('wgScript')
}?title=Special:ListFiles/${
this.pathname.replace(/^.+\//, '')
}&ilshowall=1`).text('uploads')
];
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(function () {
if (!$('input[name="wpSection"]').val()) return;
mw.hook('wikipage.content').add(async $content => {
let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text');
if (!$refs.length) return;
let ids = {};
$refs.each(function () {
ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this;
});
let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`);
$($.parseHTML(response)).find('.mw-reference-text').each(function () {
ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this);
});
});
});
mw.hook('moremenu.ready').add(config => {
$('#mm-page-purge-cache > a').on('click', e => {
e.preventDefault();
new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
}).then(() => {
location.href = mw.util.getUrl();
});
});
$('#mm-page-search-search-history-wikiblame > a').on('click', function (e) {
e.preventDefault();
let q = prompt();
if (q === null) return;
let href = this.href;
if (q) {
let removal = q[0] === '!';
if (removal) {
q = q.slice(1);
}
href += '&needle=' + encodeURIComponent(q);
if (removal) {
href += '&binary_search_inverse=on';
}
href += '&force_wikitags=on';
}
open(href, '_blank');
});
$('#mm-page-expand-templates > a').on('click auxclick', function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val());
let url = revId
? '/w/rest.php/v1/revision/' + revId
: '/w/rest.php/v1/page/' + config.page.encodedName;
$.get(url).then(response => {
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', response.source],
['wpContextTitle', config.page.name],
['wpRemoveComments', 1]
].map(([n, v]) => $('<input>').attr({
name: n,
type: 'hidden'
}).val(v))
).appendTo(document.body).trigger('submit').remove();
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' &&
mw.hook('apisandbox.formatRequest').add((...args) => {
args[4].complete = function () {
setTimeout(() => {
mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active'));
}, 100);
};
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
let section = $('input[name="wpSection"]').val();
if (section) {
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let save = () => {
let newSource = $textarea.textSelection('getContents');
if (newSource === source) {
mw.storage.session.remove('editfullpage');
} else {
mw.storage.session.setObject('editfullpage', [
mw.config.get('wgPageName'),
section,
newSource.trimEnd(),
infuseAndCall('#wpSummaryWidget', 'getValue') || '',
Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0,
Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0,
infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite'
]);
}
};
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
setInterval(() => {
mw.requestIdleCallback(save);
}, 3000);
window.addEventListener('beforeunload', save);
return;
}
let data = mw.storage.session.getObject('editfullpage');
mw.storage.session.remove('editfullpage');
console.log(data);
if (!data || data[0] !== mw.config.get('wgPageName')) return;
let isNew = data[1] === 'new';
let isLead = data[1] === '0';
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' };
let orig = [];
if (isNew) {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n';
start = source.length;
} else {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']);
let { parse } = await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'sections',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1,
formatversion: 2
});
let target = !isLead && parse.sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = parse.sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n');
start = isLead ? 0 : target.byteoffset;
} else {
newSource = source + '\n\n' + data[2] + '\n';
start = source.length;
msg = `Section restored. Couldn't find the section. The source is appended at bottom.`;
notifOpts.type = 'warn';
}
orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue');
infuseAndCall('#wpSummaryWidget', 'setValue', data[3]);
}
$textarea.textSelection('setContents', newSource);
orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected');
infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]);
orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected');
infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]);
orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue');
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]);
setTimeout(() => {
$textarea.textSelection('setSelection', { start });
});
let notif = await mw.notify($([
document.createTextNode(msg || 'Section restored.'),
$('<p>').append(
new OO.ui.ButtonWidget({
flags: 'destructive',
label: 'Discard'
}).on('click', () => {
$textarea.textSelection('setContents', source);
if (orig[0]) {
infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]);
}
infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]);
infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]);
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]);
notif.close();
}).$element
)[0]
]), notifOpts);
});
mw.config.exists('wgPostEdit') &&
mw.loader.using('mediawiki.storage', () => {
mw.storage.session.remove('editfullpage');
});
mw.config.get('wgAction') === 'history' &&
mw.hook('wikipage.content').add(async $content => {
if (!$content.has('.mw-history-line-updated').length) return;
let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href');
if (!href) {
await mw.loader.using(['mediawiki.api', 'mediawiki.util']);
let page = (await new mw.Api().get({
action: 'query',
titles: mw.config.get('wgPageName'),
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev >= page.lastrevid) return;
href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
}
$content.find('.mw-history-compareselectedversions-button').first().after(
' ',
$('<a>').attr({
class: 'unseendiff',
href: href
}).text('unseen')
);
});
(async () => {
let cspn = mw.config.get('wgCanonicalSpecialPageName');
let isBp = cspn === 'Blankpage';
if (!isBp && cspn !== 'Watchlist') return;
await mw.loader.using('mediawiki.util');
let notify = async (text, options, pn) => {
let msg = [document.createTextNode(text)];
if (pn) {
msg.push(
$('<p>').append(
$('<a>').attr('href', mw.util.getUrl(pn)).text(pn),
' ',
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'edit' }))
.text('edit')
),
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'history' }))
.text('history')
)
)
)[0]
);
}
if (isBp) {
await $.ready;
$('#mw-content-text').html(msg);
} else {
return mw.notify(msg, Object.assign(options || {}, {
tag: 'unseendiff'
}));
}
};
let getUrl = async pn => {
await mw.loader.using('mediawiki.api');
let page = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
if (!page.notificationtimestamp) {
notify(`Couldn't get the last seen time.`, {
type: 'warn'
}, pn);
return;
}
let rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (rev === page.lastrevid) {
notify('Already seen.', {
type: 'warn'
}, pn);
return;
}
if (!rev) {
rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
rvdir: 'newer',
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev) {
notify(`Couldn't get the last seen revision.`, {
type: 'warn'
}, pn);
return;
}
}
if (rev > page.lastrevid) {
notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, {
autoHideSeconds: 'long',
type: 'warn'
}, pn);
return;
}
return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
};
if (isBp) {
let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1];
if (!pn) return;
notify('Loading...', null, pn);
let href = await getUrl(pn);
if (!href) return;
notify('Redirecting...', null, pn);
location.href = href;
return;
}
let handler = async function (e) {
if (e.which > 2) return;
e.preventDefault();
let pn = this.dataset.pn;
if (!pn) {
notify(`Couldn't get the page name.`, {
type: 'error'
});
return;
}
let notifPromise = notify('Loading...', {
autoHideSeconds: 'long'
});
let href = await getUrl(pn);
if (!href) return;
$(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({
class: 'unseendiff',
href: href,
target: '_blank'
}).off('click auxclick', handler);
if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) {
open(href);
} else {
this.click();
}
(await notifPromise).close();
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner'
).each(function () {
let pn = this.dataset.targetPage ||
this.closest('[data-target-page]')?.dataset.targetPage ||
this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage;
if (!pn) return;
$('<span>').append(
$('<a>').attr({
class: 'unseendiff-loader',
href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`),
'data-pn': pn
}).on('click auxclick', handler).text('unseen')
).appendTo(
[...this.querySelectorAll('.mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
});
})();
['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
mw.loader.using('mediawiki.util', () => {
let watched = new Set();
let query = async lis => {
let titles = Object.keys(lis).slice(0, 50);
if (!titles.length) return;
await mw.loader.using('mediawiki.api');
let pages = (await new mw.Api().post({
action: 'query',
titles: titles,
prop: 'info',
inprop: 'notificationtimestamp|watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages;
for (let page of pages) {
if (!Object.hasOwn(lis, page.title)) continue;
if (page.watched) {
watched.add(page);
$(lis[page.title]).addClass('watched');
}
if (!page.notificationtimestamp) continue;
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev === page.lastrevid) continue;
if (rev > page.lastrevid) {
mw.notify($([
document.createTextNode('Invalid rev for "'),
$('<a>').attr({
href: mw.util.getUrl(page.title, { action: 'history' }),
target: '_blank'
}).text(page.title)[0],
document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`),
]), {
autoHideSeconds: 'long',
type: 'warn'
});
continue;
}
$('<span>').append(
$('<a>').attr({
class: 'unseendiff',
href: mw.util.getUrl(page.title, {
diff: page.lastrevid,
oldid: rev
})
}).text('unseen')
).appendTo(
lis[page.title].map(li => (
[...li.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ')
))
);
}
titles.forEach(title => {
delete lis[title];
});
query(lis);
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]'
).each(function () {
let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history');
let pn = link ? new URLSearchParams(link.search).get('title') : '';
$('<span>').append(
$('<a>').attr({
class: 'mw-changeslist-diff',
href: mw.util.getUrl(pn, {
diff: 'cur',
oldid: this.dataset.mwRevid
})
}).text('cur')
).appendTo(
[...this.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
if (mw.config.get('wgWikiID') === 'wikidatawiki') return;
let lis = {};
$content.find('.mw-contributions-title').each(function () {
let title = this.textContent;
if (!Object.hasOwn(lis, title)) {
lis[title] = [];
}
lis[title].push(this.closest('li'));
});
Object.keys(lis).forEach(title => {
if (watched.has(title)) {
$(lis[title]).addClass('watched');
delete lis[title];
}
});
query(lis);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript');
mw.config.get('wgWikiID') === 'metawiki' &&
(async () => {
let css = mw.loader.addStyleTag(`.wishtitle {
font-size: 90%;
font-style: italic;
word-break: break-word;
}
.wishtitle > a {
color: var(--color-warning, #886425);
}
.wishtitle > a:visited {
color: var(--border-color-warning--hover, #735421);
}
.wishtitle-declined > a {
text-decoration: line-through;
}
.wishtitle-declined > a:hover,
.wishtitle-declined > a:focus,
.mw-underline-always .wishtitle-declined > a {
text-decoration: line-through underline;
}
#watchlist-edit-form .wishtitle {
display: inline-block;
}
.mw-search-result-heading > .wishtitle,
.catchangesviewer-table .wishtitle {
display: block;
}
.catchangesviewer-table:has(.wishtitle) {
white-space: wrap;
}`);
let lang = mw.config.get('wgUserLanguage');
let titles;
let loadTitles = async () => {
await mw.loader.using('mediawiki.storage');
titles = titles || mw.storage.getObject('wishtitles');
if (titles?.lang !== lang) {
titles = { lang, w: [], fa: [] };
}
};
let updateTitles = async (crwstatuses, crwcontinue) => {
await mw.loader.using('mediawiki.api');
let params = {
action: 'query',
list: 'communityrequests-wishes',
crwlang: lang,
crwstatuses: crwstatuses,
crwprop: 'title|updated',
crwsort: 'updated',
crwdir: 'ascending',
crwlimit: 'max',
crwcontinue: crwcontinue,
formatversion: 2
};
if (!crwcontinue && !crwstatuses && titles._) {
params.crwcontinue = `|${titles._}|0`;
}
let response = await new mw.Api().get(params);
let wishes = response?.query?.['communityrequests-wishes'];
if (wishes?.length) {
let $span = $('<span>');
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = $span.html(w.title).text();
if (crwstatuses === 'declined') {
(titles.wd = titles.wd || []).push(id - 1);
}
let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1];
if (!faId) return;
titles.fa[faId - 1] = w.focusareatitle;
});
if (!crwstatuses) {
titles._ = wishes.at(-1).updated.replace(/\D/g, '');
}
}
let expiry = 86400;
if (crwstatuses || crwcontinue) {
let prev = mw.storage.getObject('_EXPIRY_wishtitles');
if (prev) {
expiry = Math.round(Date.now() / 1000) + 86400 - prev;
}
}
mw.storage.setObject('wishtitles', titles, expiry);
crwcontinue = response?.continue?.crwcontinue;
if (crwcontinue) {
await updateTitles(crwstatuses, crwcontinue);
}
};
let getTitle = id => (
id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await $.get(
`https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html`
)));
await queryTitles(xfd, xfd.titleExtractor($page));
};
let queryCat = async (xfd, gcmcontinue) => {
let response = await new mw.Api().get({
action: 'query',
prop: 'info|categories',
inprop: 'watched',
clprop: 'sortkey',
clcategories: `Category:${xfd.cat}`,
generator: 'categorymembers',
gcmtitle: `Category:${xfd.cat}`,
gcmlimit: 'max',
gcmsort: 'timestamp',
gcmdir: 'older',
gcmcontinue: gcmcontinue,
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') {
xfd.pages.push(p.title);
}
});
if (response?.continue?.gcmcontinue) {
await queryCat(xfd, response.continue.gcmcontinue);
}
};
let show = async (xfd, lastId, isCache) => {
if (xfd.portlet && isCache) return;
let portletId = 'p-xfdnotifier-' + xfd.id;
if (xfd.portlet) {
$(xfd.portlet).find('ul').empty();
if (!xfd.pages.length) return;
} else {
await $.ready;
xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId);
}
let $label = $(`#${portletId}-label`).attr('title', xfd.full);
if (xfd.page) {
$label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page)));
}
xfd.pages.forEach(p => {
let t = mw.Title.newFromText(p);
let isTalk = t.isTalkPage();
let $other = $('<a>').attr({
href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(),
title: isTalk ? 'subject' : 'talk'
}).text(isTalk ? 's' : 't');
let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a');
$('<span>').addClass('xfdnotifier-sublinks').append(
$('<span>').append($other),
$('<span>').append(
$('<a>').attr({
href: t.getUrl({ action: 'history' }),
title: 'history'
}).text('h')
)
).insertAfter(link);
});
};
mw.hook('wikipage.content').add(mw.util.throttle(async () => {
let cache = mw.storage.getObject('xfdnotifier') || {};
let lastId = 'p-tb';
for (let xfd of xfds) {
let portletId = 'p-xfdnotifier-' + xfd.id;
let now = Math.floor(Date.now() / 1000);
if (now - cache[xfd.id]?.[0] < 600) {
xfd.pages = cache[xfd.id].slice(1);
await show(xfd, lastId, true);
lastId = portletId;
continue;
}
xfd.pages = [];
if (xfd.cat) {
await queryCat(xfd);
} else if (xfd.page) {
await queryPage(xfd);
}
cache[xfd.id] = [now, ...xfd.pages];
mw.storage.setObject('xfdnotifier', cache, 604800);
await show(xfd, lastId);
lastId = portletId;
}
}, 1800000));
})();
fqec23i8xmlz4f0no4pqgnbiukupffl
735545
735544
2026-03-29T13:46:10Z
Nardog
40946
735545
javascript
text/javascript
(async function listTools() {
let pageAction = mw.config.get('wgAction');
let isView = pageAction === 'view';
let isEdit = ['edit', 'submit'].includes(pageAction);
if (!isView && !isEdit) return;
let pageType = mw.config.get('wgCanonicalSpecialPageName') ||
mw.config.get('wgNamespaceNumber');
if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') &&
!mw.config.get('wgIsRedirect') &&
!mw.config.get('wgPageName').includes('/')
) {
return;
}
await mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api',
'mediawiki.interface.helpers.styles'
]);
mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) {
font-size: 85%;
}
.listtools, .listtools a {
font-weight: normal !important;
font-style: normal;
}
.mw-datatable .listtools {
display: block;
}
.listtools + .mw-whatlinkshere-tools,
#watchlist-edit-form .listtools ~ .mw-changeslist-links,
.mw-special-DisambiguationPageLinks .listtools + a {
display: none;
}`);
let messages = Object.assign({
watched: 'Added "$1" to your watchlist',
watchFail: `Couldn't watch "$1"`,
unwatchFail: `Couldn't unwatch "$1"`
}, window.listtoolsMessages);
let getMsg = (key, ...args) => (
Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key
);
let notif;
let watchHandler = async function (e) {
e.preventDefault();
let $link = $(this);
let $wrapper = $link.parent();
$link.detach();
let params = new URLSearchParams(this.search);
let action = params.get('action');
$wrapper.text(getMsg(action + 'ing'));
let pn = params.get('title').replaceAll('_', ' ');
let promise = new mw.Api()[action](pn);
if (notif) {
notif.close();
notif = null;
}
try {
let result = await promise;
if (!result || !result[action + 'ed']) throw '';
let newAction = action === 'watch' ? 'unwatch' : 'watch';
params.set('action', newAction);
$link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`)
.attr('href', this.pathname + '?' + params)
.text(getMsg(newAction));
if (action !== 'watch') return;
let require = await mw.loader.using([
'mediawiki.notification', 'mediawiki.watchstar.widgets'
]);
notif = await mw.notify(
new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, {
message: getMsg('watched', pn)
}).$element,
{ tag: 'listtools' }
);
} catch {
notif = await mw.notify(getMsg(action + 'Fail', pn), {
tag: 'listtools',
type: 'error'
});
} finally {
$wrapper.html($link);
}
};
let extGetMain = function () {
return this.title;
};
let re = new RegExp(`(?:\\?title=|${
mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), ''))
})([^#&?]+)`);
let processed = new WeakSet();
let processLinks = ($links, module, titles) => {
let isBatch = !!titles;
titles = titles || new Set();
$links.each(function (i) {
if (processed.has(this)) return;
let $link = $links.eq(i);
let pn;
if (module.useText) {
pn = $link.text();
} else {
let match = $link.attr('href')?.match(re);
if (!match) return;
pn = decodeURIComponent(match[1]);
}
let t = mw.Title.newFromText(pn);
if (!t) return;
if (module.titlesOnly) {
let text = $link.text();
if (text !== pn.replaceAll('_', ' ') &&
(text !== t.getMainText() || t.namespace === 2)
) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.namespace < 0) return;
if ($link.hasClass('new')) {
t.missing = true;
}
titles.add(t.getSubjectPage().toText());
}
let $tools = $('<span>').addClass('listtools mw-changeslist-links')
.data('listtools', t);
tools.forEach(tool => {
addTool($tools, tool);
});
if ($link.is(':is(del, bdi) > :only-child')) {
if (module.position === 'end') {
$link.parent().parent().append(' ', $tools);
} else {
$link.parent().after(' ', $tools);
}
} else if (module.position === 'end') {
$link.parent().append(' ', $tools);
} else {
$link.after(' ', $tools);
}
if (module.post) {
module.post($tools);
}
processed.add(this);
});
if (!isBatch) {
getWatched(titles);
}
};
let tools = [
{
name: 'edit',
url: t => t.getUrl({ action: 'edit' })
},
{
name: 'hist',
url: t => !t.missing && t.getUrl({ action: 'history' })
},
{
name: 'links',
url: t => mw.util.getUrl('Special:WhatLinksHere/' + t)
},
{
name: 'watch',
url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }),
callback: watchHandler
}
];
let addTool = ($tools, tool, escapedName) => {
let t = $tools.data('listtools');
let $duplicate = escapedName &&
$tools.children('.listtools-' + escapedName);
let url = tool.url;
if (typeof url === 'function') {
url = url(t);
if (!url) {
$duplicate?.remove();
return;
}
}
let $link = $('<a>').attr('href', url).text(getMsg(tool.name));
if (t.host) {
$link.prop('host', t.host);
}
if (tool.callback) {
$link.on('click', tool.callback);
}
let $wrapper = $('<span>').addClass('listtools-' + tool.name)
.append($link);
let $next = tool.next && $tools.children('.listtools-' + tool.next);
if ($next?.length) {
$duplicate?.remove();
$next.before($wrapper);
} else if ($duplicate?.length) {
$duplicate.replaceWith($wrapper);
} else {
$tools.append($wrapper);
}
};
let extend = tool => {
if (tool.label && !Object.hasOwn(messages, tool.label)) {
messages[tool.name] = tool.label;
}
if (tool.next) {
tool.next = $.escapeSelector(tool.next);
}
let existingTool = tools.find(t => t.name === tool.name);
if (existingTool) {
Object.assign(existingTool, tool);
} else {
tools.push(tool);
}
let escapedName = existingTool && $.escapeSelector(tool.name);
let $allTools = $('.listtools');
$allTools.each(function (i) {
addTool($allTools.eq(i), tool, escapedName);
});
};
let getWatched = async titles => {
if (!Array.isArray(titles)) {
titles = [...titles].slice(0, 500);
}
if (!titles.length) return;
(await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages.forEach(page => {
if (!page.watched) return;
$(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`)
.attr('href', mw.util.getUrl(page.title, { action: 'unwatch' }))
.text(getMsg('unwatch'));
});
getWatched(titles.slice(50));
};
mw.hook('listtools.ready').fire(extend);
let catTreeCallback = (records, observer) => {
let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a');
if ($links.length) {
observer.takeRecords();
observer.disconnect();
processLinks($links, catTreeModule);
}
};
let catTreeModule = {
selector: '.CategoryTreeItem > bdi > a',
types: [14, 'CategoryTree'],
position: 'end',
post: $tools => {
$tools.parent().next('.CategoryTreeChildren').each(function () {
new MutationObserver(catTreeCallback)
.observe(this, { childList: true });
});
}
};
let modules = [
{
selector: '#mw-pages li > a, #mw-pages li > span > a',
types: [14]
},
catTreeModule,
{
selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a',
types: [6]
},
{
selector: '#mw-globalusage-result a',
types: ['GlobalUsage']
},
{
selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a',
types: ['Search']
},
{
selector: '.mw-search-createlink a',
types: ['Search'],
titlesOnly: true
},
{
selector: '#watchlist-edit-form .cdx-table td > label > a',
types: ['EditWatchlist']
},
{
selector: '.plainlinks > li > a',
types: ['AbuseLog'],
titlesOnly: true
},
{
selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)',
types: ['Allmessages'],
position: 'end'
},
{
selector: '.mw-spcontent li a',
types: ['DisambiguationPageLinks', 'Listredirects'],
titlesOnly: true
},
{
selector: 'li > a:first-child',
types: ['FileDuplicateSearch']
},
{
selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a',
types: ['LintErrors'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: 'form > ul > li > a',
types: ['Nuke'],
position: 'end',
titlesOnly: true
},
{
selector: '.page-assessments a',
types: ['PageAssessments'],
titlesOnly: true
},
{
selector: '.TablePager_col_pr_page > a',
types: ['Protectedpages'],
position: 'end'
},
{
selector: '#mw-content-text > ul a',
types: ['Protectedtitles'],
position: 'end'
},
{
selector: '.mw-fr-pending-changes-page-title',
types: ['PendingChanges'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: '#mw-content-text > ul a:first-child',
types: ['StablePages'],
position: 'end'
},
{
selector: '.TablePager_col__page a',
types: ['TopicSubscriptions']
},
{
selector: '.undeleteResult > a',
types: ['Undelete'],
position: 'end',
useText: true
},
{
selector: '.TablePager_col_img_name > a:first-child',
// types: ['Listfiles'],
position: 'end'
},
{
selector: '.mw-newpages-pagename',
post: $tools => {
let $contents = $tools.parent().contents();
$contents.slice(
$contents.index($tools) + 1,
$contents.index($contents.filter('.mw-newpages-length'))
).replaceWith(' ');
}
},
{
selector: '#mw-whatlinkshere-list li > bdi > a'
},
{
selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)',
titlesOnly: true
},
{
selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a',
types: ['Log'],
titlesOnly: true
},
{
selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a',
types: ['ComparePages'],
position: 'end'
},
{
selector: '#movepage-oldlink, #movepage-newlink',
types: ['Movepage']
},
{
selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)',
types: ['Undelete'],
useText: true
},
{
selector: '.galleryfilename, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
}());
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// }).then(response => {
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// }).always(() => {
// $wrapper.html($link);
// });
// }
// });
extend({
name: 'copy',
url: '#',
callback: function (e) {
e.preventDefault();
let text = $(this).closest('.listtools').data('listtools').toText();
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch (err) {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
}
});
});
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' &&
(function consecudiff() {
mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}');
let isHist = mw.config.get('wgAction') === 'history';
class Consecudiff {
constructor(lis, isContribs) {
this.isContribs = isContribs;
this.isEnhanced = !isHist && !isContribs &&
lis[0].classList.contains('mw-enhanced-rc');
this.threshold = isContribs ? window.consecudiffContribsThreshold || 120
: isHist ? window.consecudiffHistThreshold || 720
: window.consecudiffThreshold || 720;
this.strictMode = !isContribs &&
!!window.consecudiffDetectInterruptions;
this.diffSelector = isHist
? 'a.mw-history-histlinks-previous'
: '.mw-changeslist-diff';
this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' ||
(isHist || isContribs) && 'a.mw-changeslist-date';
this.hybridSelector = this.diffSelector;
if (this.permaSelector) {
this.hybridSelector += ', ' + this.permaSelector;
}
this.topClass = isContribs
? 'mw-contributions-current'
: 'mw-changeslist-last';
let dependencies = ['mediawiki.util'];
if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') {
dependencies.push('mediawiki.language.months');
}
mw.loader.using(dependencies, () => {
let chunks;
if (isHist) {
chunks = this.chunkByUser(lis);
} else {
chunks = [];
this.groupByTitle(lis).forEach(group => {
chunks.push(...this.chunkByUser(group));
});
}
let subchunks = [];
chunks.forEach(chunk => {
subchunks.push(...this.divideByDate(chunk));
});
let linkPairs = [];
subchunks.forEach(subchunk => {
linkPairs.push(...this.makeLinks(subchunk));
});
linkPairs.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
});
}
groupByTitle(lis) {
let selector = this.isContribs
? '.mw-contributions-title'
: '.mw-changeslist-title';
let lisByTitle = {};
lis.forEach(li => {
let link = (this.isEnhanced ? li.closest('table') : li)
.querySelector(selector);
if (!link) return;
let title = link.textContent;
if (!lisByTitle.hasOwnProperty(title)) {
lisByTitle[title] = [];
}
lisByTitle[title].push(li);
});
return Object.values(lisByTitle).filter(group => group.length > 1);
}
chunkByUser(lis) {
if (this.isSingleContribs) {
return [lis];
}
let chunks = [], lastSplitAt = 0, prevUser;
this.isSingleContribs = lis.some((li, i) => {
let link = li.querySelector('.mw-userlink');
if (!link && this.isContribs) {
return true;
}
let user = link && link.textContent;
if (!link || i && user !== prevUser) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevUser = user;
});
if (this.isSingleContribs) {
return [lis];
}
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
divideByDate(lis) {
let chunks = [], lastSplitAt = 0, prevDate;
lis.forEach((li, i) => {
let date;
if (isHist || this.isContribs) {
date = this.parseDate(
li.querySelector('.mw-changeslist-date').textContent
);
} else {
date = Date.parse(
li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z')
);
}
if (date) {
date = date / 60000;
}
if (i && prevDate - date > this.threshold) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevDate = date;
if (!this.strictMode || lastSplitAt === i) return;
let prevDiff = lis[i - 1].querySelector(this.diffSelector);
if (prevDiff) {
let prevNext = mw.util.getParamValue('oldid', prevDiff.search);
if (prevNext !== li.dataset.mwRevid) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
}
});
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
makeLinks(lis) {
let count = lis.length;
let firstPerma;
let start = lis.findIndex(li => (
firstPerma = li.querySelector(this.hybridSelector)
));
if (start === -1 || count - start < 2) return [];
let end, lastDiff;
for (let i = count - 1; i > start; i--) {
if (!isHist && !this.isContribs) {
lastDiff = lis[i].querySelector(this.diffSelector);
if (lastDiff ||
lis[i].classList.contains('mw-changeslist-src-mw-new')
) {
end = i + 1;
break;
}
}
if (this.permaSelector && lis[i].querySelector(this.permaSelector)) {
end = i + 1;
break;
}
}
if (!end) return [];
count = end - start;
let params = { diff: lis[start].dataset.mwRevid };
if (lastDiff) {
params.oldid = mw.util.getParamValue('oldid', lastDiff.search);
} else {
params.oldid = lis[end - 1].dataset.mwRevid;
if (isHist && lis[end - 1].querySelector(this.diffSelector) ||
this.isContribs && !lis[end - 1].querySelector('.newpage')
) {
params.direction = 'prev';
}
}
let title = !isHist && mw.util.getParamValue('title', firstPerma.search);
let url = mw.util.getUrl(title, params);
let classes = 'consecudiff';
if (!isHist && lis[start].classList.contains(this.topClass)) {
classes += ' consecudiff-top';
}
return lis.slice(start, end).map((li, i) => [
$('<span>').addClass(classes).append(
$('<a>')
.attr('href', url)
.text(this.convertNumber(count - i + '/' + count))
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
parseDate(s) {
let date = Date.parse(s);
if (date) {
return date;
}
if (s.includes(',')) date = Date.parse(s.replace(',', ''));
if (date) {
return date;
}
if (mw.loader.getState('mediawiki.language.months') !== 'ready') return;
s = s.replace(/\D/g, c => {
let n = mw.language.convertNumber(c, true);
return Number.isNaN(n) ? c : n;
});
let h, m;
s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => {
h = $1;
m = $2;
return ' ';
});
if (!h) return;
let y, dateFirst;
s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => {
y = $2;
dateFirst = /\d/.test($1);
return $1 + ' ';
});
if (!y) return;
let mo, d;
if (dateFirst) {
[d, s] = this.getDate(s);
if (!d) return;
[mo, s] = this.getMonth(s);
if (mo === -1) return;
} else {
[mo, s] = this.getMonth(s);
if (mo === -1) return;
[d, s] = this.getDate(s);
if (!d) return;
}
return new Date(y, mo, d, h, m).getTime();
}
getMonth(s) {
if (!this.months) {
this.months = mw.language.months.abbrev
.concat(mw.language.months.names, mw.language.months.genitive)
.reverse();
}
let mo = this.months.findIndex(mn => {
let temp = s.replace(mn, ' ');
if (temp !== s) {
s = temp;
return true;
}
});
if (mo === -1) {
let [numeric, temp] = this.getDate(s);
numeric = parseInt(numeric);
if (numeric > 0 && numeric < 13) {
mo = numeric - 1;
s = temp;
}
} else {
mo = 11 - mo % 12;
}
return [mo, s];
}
getDate(s) {
let d;
s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => {
d = $2;
return $1 + ' ';
});
return [d, s];
}
convertNumber(num) {
try {
return mw.language.convertNumber(num);
} catch (e) {
return num;
}
}
}
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-body').each(function () {
let lis = this.querySelectorAll('.mw-contributions-list > li');
if (lis.length > 1) {
new Consecudiff([...lis], !isHist);
}
});
if (isHist) return;
let $lists = $content.filter('.mw-changeslist');
if (!$lists.length) {
$lists = $content.find('.mw-changeslist');
}
$lists.each(function () {
let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]');
if (lis.length > 1) {
new Consecudiff([...lis]);
}
});
});
}());
if (mw.config.get('wgNamespaceNumber') === 14 && (
mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId')
)) {
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter',
'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement',
'mediawiki.interface.helpers.styles', 'user.options'
]);
}
$(function moveHistory() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.moveHistoryDialog) {
window.moveHistoryDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter',
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions',
'mediawiki.interface.helpers.styles'
]);
});
});
});
$(function sectionSearch() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.sectionSearchDialog) {
window.sectionSearchDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows',
'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget'
]);
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' &&
mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() {
mw.hook('wikipage.content').add($content => {
let $table = $content.find('.mw-centralauth-wikislist').has('td');
if (!$table.length) return;
$table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
[10, 828].includes(mw.config.get('wgNamespaceNumber')) &&
!mw.config.get('wgTitle').endsWith('/doc') &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// $(function templatePreviewGuard() {
// let button = document.querySelector('input[name="wpTemplateSandboxPreview"]');
// if (!button) return;
// let proceed;
// button.addEventListener('click', e => {
// if (proceed) {
// proceed = false;
// return;
// }
// e.preventDefault();
// e.stopPropagation();
// let formData = new FormData(button.form);
// let page = formData.get('wpTemplateSandboxPage');
// let temp = formData.get('wpTemplateSandboxTemplate');
// if (!page || !temp) return;
// mw.loader.using('mediawiki.api').then(() => (
// new mw.Api().get({
// action: 'query',
// titles: page,
// prop: 'templates',
// tltemplates: temp,
// formatversion: 2
// })
// )).always(response => {
// if (((((response || {}).query || {}).pages || [])[0] || {}).templates ||
// confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`)
// ) {
// proceed = true;
// button.click();
// }
// });
// }, true);
// if (!mw.config.get('wgArticleId')) return;
// let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget');
// if (!widgetEl) return;
// let pn = mw.config.get('wgPageName').replace(/_/g, ' ');
// mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => (
// new mw.Api().get({
// action: 'query',
// titles: pn,
// prop: 'transcludedin',
// tiprop: 'title',
// tilimit: 'max',
// formatversion: 2
// })
// )).then(response => {
// if (!response.batchcomplete) return;
// let pages = response.query.pages[0].transcludedin
// .filter(o => o.title !== pn);
// if (!pages.length) return;
// let widget = OO.ui.infuse(widgetEl);
// if (pages.length === 1) {
// widget.setValue(pages[0].title);
// return;
// }
// widget.$element.replaceWith(
// new OO.ui.ComboBoxInputWidget({
// id: 'wpTemplateSandboxPage',
// maxlength: widget.$input.prop('maxLength'),
// name: widget.$input.prop('name'),
// options: pages
// .sort((a, b) => a.ns - b.ns || -(a.title < b.title))
// .map(o => ({ data: o.title })),
// placeholder: widget.$input.prop('placeholder'),
// tabIndex: widget.getTabIndex(),
// value: widget.getValue()
// }).on('enter', e => {
// e.preventDefault();
// button.click();
// }).$element
// );
// });
// });
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoSectionLink.js&action=raw&ctype=text/javascript', 's');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(function copyRevId() {
let handler = function (e) {
e.preventDefault();
let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid ||
this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!text) return;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
document.execCommand('copy');
$input.remove();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id')
)
);
});
}());
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
let handler = async function (e) {
e.preventDefault();
let td = this.closest('.diff td');
let rev = td
? td.querySelector('[data-mw-revid]')?.dataset.mwRevid
: this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!rev) {
mw.notify(`Couldn't get the revision.`, {
tag: 'markasunseen',
type: 'error'
});
return;
}
let pn = td
? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title')
: mw.config.get('wgPageName');
if (!pn) return;
await mw.loader.using('mediawiki.api');
let result = (await new mw.Api().postWithEditToken({
action: 'setnotificationtimestamp',
[td ? 'newerthanrevid' : 'torevid']: rev,
titles: pn,
formatversion: 2
})).setnotificationtimestamp?.[0];
if (Object.hasOwn(result, 'notificationtimestamp')) {
mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'success'
});
} else if (result?.notwatched) {
mw.notify('This page is not on your watchlist.', {
tag: 'markasunseen',
type: 'warn'
});
} else {
mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'error'
});
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions after this one as unseen'
}).on('click', handler).text('unseen'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions since this one as unseen'
}).on('click', handler).text('unseen')
)
);
});
})();
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) ||
(mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) &&
mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() {
let handler = function (e) {
e.preventDefault();
let parent = this.closest('li, td');
let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0];
if (!ts) return;
let user = parent.querySelector('.mw-userlink').textContent;
if (mw.util.isIPv6Address(user)) {
user = user.toUpperCase();
}
let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned';
let text = `{{subst:${temp}|${user}|${ts}}}`;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () {
if (mw.config.get('wgWikiID') === 'metawiki') {
return true;
}
let link = this.querySelector('strong > a') ||
this.parentElement.querySelector('#differences-prevlink, #differences-nextlink');
if (!link) return;
let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search));
return t.isTalkPage() || t.namespace === 4;
}).append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig')
)
);
});
});
// mw.config.get('wgAction') === 'history' &&
// mw.loader.using('mediawiki.util', function () {
// mw.hook('wikipage.content').add($content => {
// $content.find('a.mw-changeslist-date').after(function () {
// return [
// ' (',
// $('<a>').attr('href', mw.util.getUrl(null, {
// action: 'edit',
// oldid: this.closest('li').dataset.mwRevid
// })).text('e'),
// ')'
// ];
// });
// });
// });
// ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
// mw.hook('wikipage.content').add($content => {
// $content.find('.mw-changeslist-history').parent().after(function () {
// return $('<span>').append(
// $('<a>').attr(
// 'href',
// this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit'
// ).text('e')
// );
// });
// });
if (screen.width < 500) {
mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)}@font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}');
location.hash && $(() => {
let target = document.querySelector(':target');
if (target?.getBoundingClientRect().top < 0) {
target.scrollIntoView();
}
});
}
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(mw.config.exists('wgCodeEditorCurrentLanguage') ||
mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') &&
(function saveNEdit() {
let notif;
$(document.body).on('click', '#wpSave', async function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
e.originalEvent?.defaultPrevented
) {
return;
}
e.preventDefault();
await mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core'
]);
let button = OO.ui.infuse(this.parentElement).setDisabled(true);
let $textarea = $('#wpTextbox1');
let text = $textarea.textSelection('getContents');
let $summary = $('#wpSummary');
let formData = new FormData(this.form);
let promise = new mw.Api().postWithEditToken({
action: 'edit',
title: mw.config.get('wgPageName'),
text: text,
section: formData.get('wpSection') || undefined,
summary: $summary.textSelection('getContents'),
[$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1,
baserevid: formData.get('editRevId'),
basetimestamp: formData.get('wpEdittime'),
starttimestamp: formData.get('wpStarttime'),
watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch',
watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined,
undo: formData.get('wpUndidRevision') || undefined,
undoafter: formData.get('wpUndoAfter') || undefined,
contentformat: formData.get('format'),
contentmodel: formData.get('model'),
assertuser: mw.config.get('wgUserName'),
formatversion: 2
});
notif?.close();
notif = null;
try {
let response = await promise;
if (response?.edit?.result !== 'Success') throw '';
$('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove();
$textarea.data('origtext', text).prop('defaultValue', text);
$summary.val($summary.prop('defaultValue'));
if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') {
let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js'];
storage.deleteData(mw.config.get('wgPageName'));
storage.closeDatabase();
}
notif = await mw.notify(response.edit.nochange ? 'No change' : [
document.createTextNode('Saved'),
$('<p>').append(
new OO.ui.ButtonWidget({
href: mw.util.getUrl(),
target: '_blank',
label: 'View'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, {
diff: response.edit.newrevid || 'cur',
diffonly: 1
}),
target: '_blank',
label: 'Diff'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, { action: 'history' }),
target: '_blank',
label: 'History'
}).$element
)[0]
], { tag: 'savenedit' });
} catch (error) {
notif = await mw.notify(error?.error?.info || error || 'Save failed', {
autoHideSeconds: 'long',
tag: 'savenedit',
type: 'error'
});
} finally {
button.setDisabled();
}
});
}());
mw.config.get('wgNamespaceNumber') === 0 &&
mw.config.get('wgAction') === 'view' &&
mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) &&
$(() => {
let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, ''));
let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`);
if (!$links.length) return;
let titles = {};
$links = $links.filter(function () {
let text = this.textContent;
return !(titles[text] = Object.hasOwn(titles, text));
});
mw.notify(
$links.length === 1
? $links.clone()
: $('<ul>').append($links.clone().wrap('<li>').parent()),
{ autoHideSeconds: 'long' }
);
});
['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
$.when($.ready, mw.loader.using([
'user.options', 'mediawiki.util', 'mediawiki.api'
])).then(function rcMuter() {
let os = mw.user.options.get('userjs-rcmuter');
let set = new Set(os && os.split('|'));
let save = () => {
let ns = [...set].join('|');
if (ns === mw.user.options.get('userjs-rcmuter')) return;
new mw.Api().saveOption('userjs-rcmuter', ns);
mw.user.options.set('userjs-rcmuter', ns);
$edit.attr('data-rcmuter', set.size);
};
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
'data-rcmuter': set.size
}).text('Edit muted').on('click', e => {
e.preventDefault();
mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]).then(() => OO.ui.getWindowManager().getWindow('message')).then(dialog => {
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...set]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
let instance = dialog.open({
message: $([
document.createTextNode('Muted users:'),
multiselect.$element[0]
]),
size: 'medium'
});
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().menu.toggle(false);
});
});
instance.closed.then(result => {
if (!result || result.action !== 'accept') return;
set = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
});
let buttonsShown;
let $toggleButtons = $('<a>').attr('href', '#').text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (buttonsShown) {
mw.hook('wikipage.content').remove(addButtons);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addButtons);
this.textContent = 'Hide toggle buttons';
}
buttonsShown = !buttonsShown;
});
let $toggle = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleSpan = $('<span>').hide().append($toggle);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleButtons),
$toggleSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-userlink ~ .mw-usertoollinks, .mw-changeslist-line-inner-userLink ~ .mw-changeslist-line-inner-userTalkLink')
.prevAll('.mw-userlink, .mw-changeslist-line-inner-userLink')
.last().text().trim();
if (!user) {
mw.notify(`Can't retrieve the username.`, {
tag: 'rcmuter',
type: 'error'
});
return;
}
let muting = this.parentElement.classList.toggle('rcmuter-unmute');
set[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addButtons = $content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) {
$content = $('#mw-content-text');
if ($content.has('.rcmuter-toggle').length) return;
}
let $tools = $content.find('.mw-usertoollinks.mw-changeslist-links');
let $muted = $tools.filter('.rcmuter-muted *');
$tools.not($muted).append(
$('<span>').addClass('rcmuter-toggle').append(
$('<a>').attr('href', '#').text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr('href', '#').text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let muted = set.has(this.textContent);
if (muted) mutedCount++;
return muted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!set.size) {
$toggleSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-userlink:only-child')
.filter(filter).closest('table').addClass('rcmuter-muted');
$content.find('.mw-userlink:not(.changedby > *, .comment *, .rcmuter-muted *)')
.filter(filter).closest('.mw-changeslist-line, table').addClass('rcmuter-muted')
.closest('table.mw-enhanced-rc').find('.changedby > .mw-userlink').filter(filter).addClass('rcmuter-muted');
$toggleSpan.toggle(!!mutedCount);
$toggle.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() {
if (!document.getElementById('p-tb')) return;
let messages = Object.assign({
portlet: 'RefRenamer',
loading: 'Loading RefRenamer...'
}, window.refrenamerMessages);
let clicked;
mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.refRenamer) {
window.refRenamer();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
if (['edit', 'submit'].includes(mw.config.get('wgAction'))) {
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's');
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's');
}
mw.config.get('wgAction') !== 'history' &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's');
mw.config.exists('wgDiffNewId') &&
mw.config.get('wgDiscussionToolsFeaturesEnabled') &&
(function () {
let data = {}, clickHandler, autoClear, run;
window.dtc = data;
let highlight = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js']
.highlightNewComments(mw.dt.pageThreads, true, ids);
if (clickHandler) {
$(document.body).off('click', clickHandler);
return;
}
$._data(document.body, 'events').click.some(o => {
if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) {
$(document.body).off('click', o.handler);
clickHandler = o.handler;
return true;
}
});
$._data(window, 'events').popstate.some(o => {
if (String(o.handler).includes('highlighter.highlightTargetComment(')) {
$(window).off('popstate', o.handler);
return true;
}
});
};
let scroll = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
let yToSpan = Object.fromEntries(
ids.map(id => document.getElementById(id)).filter(Boolean)
.map(span => [span.getBoundingClientRect().y, span])
);
let ys = Object.keys(yToSpan);
if (!ys.length) return;
let lower = ys.filter(y => y > 10);
if (!lower.length ||
Math.max(...lower) < document.documentElement.clientHeight
) {
yToSpan[Math.min(...ys)].scrollIntoView();
} else {
yToSpan[Math.min(...lower)].scrollIntoView();
}
};
let scrollToNext = function (e) {
e.preventDefault();
let revId = mw.config.get('wgDiffOldId');
if (!revId || !data[revId]) return;
let i = data[revId].indexOf(
this.closest('[data-mw-thread-id]').dataset.mwThreadId
);
if (i === -1) return;
let next = data[revId][i + 1] || data[revId][0];
document.getElementById(next).scrollIntoView();
};
mw.hook('wikipage.content').add(async $content => {
let revId = mw.config.get('wgDiffOldId');
if (!revId) return;
let param = new URLSearchParams(location.search).get('diffonly');
if (param && param !== '0') return;
if (data[revId]) {
highlight(revId);
return;
}
await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']);
let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp'));
data[revId] = mw.dt.pageThreads.getCommentItems()
.filter(c => c.timestamp > begin).map(c => c.id);
if (!data[revId].length) return;
await new Promise(setTimeout);
highlight(revId);
$content.find('.ext-discussiontools-init-replylink-buttons').filter(function () {
return data[revId].includes(this.dataset.mwThreadId);
}).children('span:last-of-type').before(
' | ',
$('<a>').attr({
href: '#',
role: 'button'
}).text('next').on('click', scrollToNext)
);
if (run || !document.getElementById('p-tb')) return;
run = true;
let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext');
portlet.firstElementChild.addEventListener('click', e => {
e.preventDefault();
scroll(mw.config.get('wgDiffOldId'));
});
mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => {
e.preventDefault();
autoClear = !autoClear;
if (autoClear) {
$(document.body).on('click', clickHandler)[0].click();
} else {
highlight(mw.config.get('wgDiffOldId'));
}
});
mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`);
});
}());
mw.config.get('wgNamespaceNumber') === 6 &&
mw.config.get('wgAction') === 'view' &&
mw.hook('wikipage.content').add($content => {
$content.find('.filehistory .mw-usertoollinks-contribs').after(function () {
return [
' | ',
$('<a>').attr('href', `${
mw.config.get('wgScript')
}?title=Special:ListFiles/${
this.pathname.replace(/^.+\//, '')
}&ilshowall=1`).text('uploads')
];
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(function () {
if (!$('input[name="wpSection"]').val()) return;
mw.hook('wikipage.content').add(async $content => {
let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text');
if (!$refs.length) return;
let ids = {};
$refs.each(function () {
ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this;
});
let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`);
$($.parseHTML(response)).find('.mw-reference-text').each(function () {
ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this);
});
});
});
mw.hook('moremenu.ready').add(config => {
$('#mm-page-purge-cache > a').on('click', e => {
e.preventDefault();
new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
}).then(() => {
location.href = mw.util.getUrl();
});
});
$('#mm-page-search-search-history-wikiblame > a').on('click', function (e) {
e.preventDefault();
let q = prompt();
if (q === null) return;
let href = this.href;
if (q) {
let removal = q[0] === '!';
if (removal) {
q = q.slice(1);
}
href += '&needle=' + encodeURIComponent(q);
if (removal) {
href += '&binary_search_inverse=on';
}
href += '&force_wikitags=on';
}
open(href, '_blank');
});
$('#mm-page-expand-templates > a').on('click auxclick', function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val());
let url = revId
? '/w/rest.php/v1/revision/' + revId
: '/w/rest.php/v1/page/' + config.page.encodedName;
$.get(url).then(response => {
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', response.source],
['wpContextTitle', config.page.name],
['wpRemoveComments', 1]
].map(([n, v]) => $('<input>').attr({
name: n,
type: 'hidden'
}).val(v))
).appendTo(document.body).trigger('submit').remove();
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' &&
mw.hook('apisandbox.formatRequest').add((...args) => {
args[4].complete = function () {
setTimeout(() => {
mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active'));
}, 100);
};
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
let section = $('input[name="wpSection"]').val();
if (section) {
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let save = () => {
let newSource = $textarea.textSelection('getContents');
if (newSource === source) {
mw.storage.session.remove('editfullpage');
} else {
mw.storage.session.setObject('editfullpage', [
mw.config.get('wgPageName'),
section,
newSource.trimEnd(),
infuseAndCall('#wpSummaryWidget', 'getValue') || '',
Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0,
Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0,
infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite'
]);
}
};
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
setInterval(() => {
mw.requestIdleCallback(save);
}, 3000);
window.addEventListener('beforeunload', save);
return;
}
let data = mw.storage.session.getObject('editfullpage');
mw.storage.session.remove('editfullpage');
console.log(data);
if (!data || data[0] !== mw.config.get('wgPageName')) return;
let isNew = data[1] === 'new';
let isLead = data[1] === '0';
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' };
let orig = [];
if (isNew) {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n';
start = source.length;
} else {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']);
let { parse } = await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'sections',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1,
formatversion: 2
});
let target = !isLead && parse.sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = parse.sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n');
start = isLead ? 0 : target.byteoffset;
} else {
newSource = source + '\n\n' + data[2] + '\n';
start = source.length;
msg = `Section restored. Couldn't find the section. The source is appended at bottom.`;
notifOpts.type = 'warn';
}
orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue');
infuseAndCall('#wpSummaryWidget', 'setValue', data[3]);
}
$textarea.textSelection('setContents', newSource);
orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected');
infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]);
orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected');
infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]);
orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue');
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]);
setTimeout(() => {
$textarea.textSelection('setSelection', { start });
});
let notif = await mw.notify($([
document.createTextNode(msg || 'Section restored.'),
$('<p>').append(
new OO.ui.ButtonWidget({
flags: 'destructive',
label: 'Discard'
}).on('click', () => {
$textarea.textSelection('setContents', source);
if (orig[0]) {
infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]);
}
infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]);
infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]);
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]);
notif.close();
}).$element
)[0]
]), notifOpts);
});
mw.config.exists('wgPostEdit') &&
mw.loader.using('mediawiki.storage', () => {
mw.storage.session.remove('editfullpage');
});
mw.config.get('wgAction') === 'history' &&
mw.hook('wikipage.content').add(async $content => {
if (!$content.has('.mw-history-line-updated').length) return;
let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href');
if (!href) {
await mw.loader.using(['mediawiki.api', 'mediawiki.util']);
let page = (await new mw.Api().get({
action: 'query',
titles: mw.config.get('wgPageName'),
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev >= page.lastrevid) return;
href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
}
$content.find('.mw-history-compareselectedversions-button').first().after(
' ',
$('<a>').attr({
class: 'unseendiff',
href: href
}).text('unseen')
);
});
(async () => {
let cspn = mw.config.get('wgCanonicalSpecialPageName');
let isBp = cspn === 'Blankpage';
if (!isBp && cspn !== 'Watchlist') return;
await mw.loader.using('mediawiki.util');
let notify = async (text, options, pn) => {
let msg = [document.createTextNode(text)];
if (pn) {
msg.push(
$('<p>').append(
$('<a>').attr('href', mw.util.getUrl(pn)).text(pn),
' ',
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'edit' }))
.text('edit')
),
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'history' }))
.text('history')
)
)
)[0]
);
}
if (isBp) {
await $.ready;
$('#mw-content-text').html(msg);
} else {
return mw.notify(msg, Object.assign(options || {}, {
tag: 'unseendiff'
}));
}
};
let getUrl = async pn => {
await mw.loader.using('mediawiki.api');
let page = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
if (!page.notificationtimestamp) {
notify(`Couldn't get the last seen time.`, {
type: 'warn'
}, pn);
return;
}
let rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (rev === page.lastrevid) {
notify('Already seen.', {
type: 'warn'
}, pn);
return;
}
if (!rev) {
rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
rvdir: 'newer',
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev) {
notify(`Couldn't get the last seen revision.`, {
type: 'warn'
}, pn);
return;
}
}
if (rev > page.lastrevid) {
notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, {
autoHideSeconds: 'long',
type: 'warn'
}, pn);
return;
}
return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
};
if (isBp) {
let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1];
if (!pn) return;
notify('Loading...', null, pn);
let href = await getUrl(pn);
if (!href) return;
notify('Redirecting...', null, pn);
location.href = href;
return;
}
let handler = async function (e) {
if (e.which > 2) return;
e.preventDefault();
let pn = this.dataset.pn;
if (!pn) {
notify(`Couldn't get the page name.`, {
type: 'error'
});
return;
}
let notifPromise = notify('Loading...', {
autoHideSeconds: 'long'
});
let href = await getUrl(pn);
if (!href) return;
$(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({
class: 'unseendiff',
href: href,
target: '_blank'
}).off('click auxclick', handler);
if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) {
open(href);
} else {
this.click();
}
(await notifPromise).close();
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner'
).each(function () {
let pn = this.dataset.targetPage ||
this.closest('[data-target-page]')?.dataset.targetPage ||
this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage;
if (!pn) return;
$('<span>').append(
$('<a>').attr({
class: 'unseendiff-loader',
href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`),
'data-pn': pn
}).on('click auxclick', handler).text('unseen')
).appendTo(
[...this.querySelectorAll('.mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
});
})();
['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
mw.loader.using('mediawiki.util', () => {
let watched = new Set();
let query = async lis => {
let titles = Object.keys(lis).slice(0, 50);
if (!titles.length) return;
await mw.loader.using('mediawiki.api');
let pages = (await new mw.Api().post({
action: 'query',
titles: titles,
prop: 'info',
inprop: 'notificationtimestamp|watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages;
for (let page of pages) {
if (!Object.hasOwn(lis, page.title)) continue;
if (page.watched) {
watched.add(page);
$(lis[page.title]).addClass('watched');
}
if (!page.notificationtimestamp) continue;
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev === page.lastrevid) continue;
if (rev > page.lastrevid) {
mw.notify($([
document.createTextNode('Invalid rev for "'),
$('<a>').attr({
href: mw.util.getUrl(page.title, { action: 'history' }),
target: '_blank'
}).text(page.title)[0],
document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`),
]), {
autoHideSeconds: 'long',
type: 'warn'
});
continue;
}
$('<span>').append(
$('<a>').attr({
class: 'unseendiff',
href: mw.util.getUrl(page.title, {
diff: page.lastrevid,
oldid: rev
})
}).text('unseen')
).appendTo(
lis[page.title].map(li => (
[...li.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ')
))
);
}
titles.forEach(title => {
delete lis[title];
});
query(lis);
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]'
).each(function () {
let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history');
let pn = link ? new URLSearchParams(link.search).get('title') : '';
$('<span>').append(
$('<a>').attr({
class: 'mw-changeslist-diff',
href: mw.util.getUrl(pn, {
diff: 'cur',
oldid: this.dataset.mwRevid
})
}).text('cur')
).appendTo(
[...this.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
if (mw.config.get('wgWikiID') === 'wikidatawiki') return;
let lis = {};
$content.find('.mw-contributions-title').each(function () {
let title = this.textContent;
if (!Object.hasOwn(lis, title)) {
lis[title] = [];
}
lis[title].push(this.closest('li'));
});
Object.keys(lis).forEach(title => {
if (watched.has(title)) {
$(lis[title]).addClass('watched');
delete lis[title];
}
});
query(lis);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript');
mw.config.get('wgWikiID') === 'metawiki' &&
(async () => {
let css = mw.loader.addStyleTag(`.wishtitle {
font-size: 90%;
font-style: italic;
word-break: break-word;
}
.wishtitle > a {
color: var(--color-warning, #886425);
}
.wishtitle > a:visited {
color: var(--border-color-warning--hover, #735421);
}
.wishtitle-declined > a {
text-decoration: line-through;
}
.wishtitle-declined > a:hover,
.wishtitle-declined > a:focus,
.mw-underline-always .wishtitle-declined > a {
text-decoration: line-through underline;
}
#watchlist-edit-form .wishtitle {
display: inline-block;
}
.mw-search-result-heading > .wishtitle,
.catchangesviewer-table .wishtitle {
display: block;
}
.catchangesviewer-table:has(.wishtitle) {
white-space: wrap;
}`);
let lang = mw.config.get('wgUserLanguage');
let titles;
let loadTitles = async () => {
await mw.loader.using('mediawiki.storage');
titles = titles || mw.storage.getObject('wishtitles');
if (titles?.lang !== lang) {
titles = { lang, w: [], fa: [] };
}
};
let updateTitles = async (crwstatuses, crwcontinue) => {
await mw.loader.using('mediawiki.api');
let params = {
action: 'query',
list: 'communityrequests-wishes',
crwlang: lang,
crwstatuses: crwstatuses,
crwprop: 'title|updated',
crwsort: 'updated',
crwdir: 'ascending',
crwlimit: 'max',
crwcontinue: crwcontinue,
formatversion: 2
};
if (!crwcontinue && !crwstatuses && titles._) {
params.crwcontinue = `|${titles._}|0`;
}
let response = await new mw.Api().get(params);
let wishes = response?.query?.['communityrequests-wishes'];
if (wishes?.length) {
let $span = $('<span>');
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = $span.html(w.title).text();
if (crwstatuses === 'declined') {
(titles.wd = titles.wd || []).push(id - 1);
}
let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1];
if (!faId) return;
titles.fa[faId - 1] = w.focusareatitle;
});
if (!crwstatuses) {
titles._ = wishes.at(-1).updated.replace(/\D/g, '');
}
}
let expiry = 86400;
if (crwstatuses || crwcontinue) {
let prev = mw.storage.getObject('_EXPIRY_wishtitles');
if (prev) {
expiry = Math.round(Date.now() / 1000) + 86400 - prev;
}
}
mw.storage.setObject('wishtitles', titles, expiry);
crwcontinue = response?.continue?.crwcontinue;
if (crwcontinue) {
await updateTitles(crwstatuses, crwcontinue);
}
};
let getTitle = id => (
id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await $.get(
`https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html`
)));
await queryTitles(xfd, xfd.titleExtractor($page));
};
let queryCat = async (xfd, gcmcontinue) => {
let response = await new mw.Api().get({
action: 'query',
prop: 'info|categories',
inprop: 'watched',
clprop: 'sortkey',
clcategories: `Category:${xfd.cat}`,
generator: 'categorymembers',
gcmtitle: `Category:${xfd.cat}`,
gcmlimit: 'max',
gcmsort: 'timestamp',
gcmdir: 'older',
gcmcontinue: gcmcontinue,
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') {
xfd.pages.push(p.title);
}
});
if (response?.continue?.gcmcontinue) {
await queryCat(xfd, response.continue.gcmcontinue);
}
};
let show = async (xfd, lastId, isCache) => {
if (xfd.portlet && isCache) return;
let portletId = 'p-xfdnotifier-' + xfd.id;
if (xfd.portlet) {
$(xfd.portlet).find('ul').empty();
if (!xfd.pages.length) return;
} else {
await $.ready;
xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId);
}
let $label = $(`#${portletId}-label`).attr('title', xfd.full);
if (xfd.page) {
$label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page)));
}
xfd.pages.forEach(p => {
let t = mw.Title.newFromText(p);
let isTalk = t.isTalkPage();
let $other = $('<a>').attr({
href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(),
title: isTalk ? 'subject' : 'talk'
}).text(isTalk ? 's' : 't');
let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a');
$('<span>').addClass('xfdnotifier-sublinks').append(
$('<span>').append($other),
$('<span>').append(
$('<a>').attr({
href: t.getUrl({ action: 'history' }),
title: 'history'
}).text('h')
)
).insertAfter(link);
});
};
mw.hook('wikipage.content').add(mw.util.throttle(async () => {
let cache = mw.storage.getObject('xfdnotifier') || {};
let lastId = 'p-tb';
for (let xfd of xfds) {
let portletId = 'p-xfdnotifier-' + xfd.id;
let now = Math.floor(Date.now() / 1000);
if (now - cache[xfd.id]?.[0] < 600) {
xfd.pages = cache[xfd.id].slice(1);
await show(xfd, lastId, true);
lastId = portletId;
continue;
}
xfd.pages = [];
if (xfd.cat) {
await queryCat(xfd);
} else if (xfd.page) {
await queryPage(xfd);
}
cache[xfd.id] = [now, ...xfd.pages];
mw.storage.setObject('xfdnotifier', cache, 604800);
await show(xfd, lastId);
lastId = portletId;
}
}, 1800000));
})();
se2dbev0mdm1osiahkmjv0s00nizsh3
735546
735545
2026-03-29T13:46:17Z
Nardog
40946
735546
javascript
text/javascript
(async function listTools() {
let pageAction = mw.config.get('wgAction');
let isView = pageAction === 'view';
let isEdit = ['edit', 'submit'].includes(pageAction);
if (!isView && !isEdit) return;
let pageType = mw.config.get('wgCanonicalSpecialPageName') ||
mw.config.get('wgNamespaceNumber');
if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') &&
!mw.config.get('wgIsRedirect') &&
!mw.config.get('wgPageName').includes('/')
) {
return;
}
await mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api',
'mediawiki.interface.helpers.styles'
]);
mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) {
font-size: 85%;
}
.listtools, .listtools a {
font-weight: normal !important;
font-style: normal;
}
.mw-datatable .listtools {
display: block;
}
.listtools + .mw-whatlinkshere-tools,
#watchlist-edit-form .listtools ~ .mw-changeslist-links,
.mw-special-DisambiguationPageLinks .listtools + a {
display: none;
}`);
let messages = Object.assign({
watched: 'Added "$1" to your watchlist',
watchFail: `Couldn't watch "$1"`,
unwatchFail: `Couldn't unwatch "$1"`
}, window.listtoolsMessages);
let getMsg = (key, ...args) => (
Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key
);
let notif;
let watchHandler = async function (e) {
e.preventDefault();
let $link = $(this);
let $wrapper = $link.parent();
$link.detach();
let params = new URLSearchParams(this.search);
let action = params.get('action');
$wrapper.text(getMsg(action + 'ing'));
let pn = params.get('title').replaceAll('_', ' ');
let promise = new mw.Api()[action](pn);
if (notif) {
notif.close();
notif = null;
}
try {
let result = await promise;
if (!result || !result[action + 'ed']) throw '';
let newAction = action === 'watch' ? 'unwatch' : 'watch';
params.set('action', newAction);
$link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`)
.attr('href', this.pathname + '?' + params)
.text(getMsg(newAction));
if (action !== 'watch') return;
let require = await mw.loader.using([
'mediawiki.notification', 'mediawiki.watchstar.widgets'
]);
notif = await mw.notify(
new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, {
message: getMsg('watched', pn)
}).$element,
{ tag: 'listtools' }
);
} catch {
notif = await mw.notify(getMsg(action + 'Fail', pn), {
tag: 'listtools',
type: 'error'
});
} finally {
$wrapper.html($link);
}
};
let extGetMain = function () {
return this.title;
};
let re = new RegExp(`(?:\\?title=|${
mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), ''))
})([^#&?]+)`);
let processed = new WeakSet();
let processLinks = ($links, module, titles) => {
let isBatch = !!titles;
titles = titles || new Set();
$links.each(function (i) {
if (processed.has(this)) return;
let $link = $links.eq(i);
let pn;
if (module.useText) {
pn = $link.text();
} else {
let match = $link.attr('href')?.match(re);
if (!match) return;
pn = decodeURIComponent(match[1]);
}
let t = mw.Title.newFromText(pn);
if (!t) return;
if (module.titlesOnly) {
let text = $link.text();
if (text !== pn.replaceAll('_', ' ') &&
(text !== t.getMainText() || t.namespace === 2)
) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.namespace < 0) return;
if ($link.hasClass('new')) {
t.missing = true;
}
titles.add(t.getSubjectPage().toText());
}
let $tools = $('<span>').addClass('listtools mw-changeslist-links')
.data('listtools', t);
tools.forEach(tool => {
addTool($tools, tool);
});
if ($link.is(':is(del, bdi) > :only-child')) {
if (module.position === 'end') {
$link.parent().parent().append(' ', $tools);
} else {
$link.parent().after(' ', $tools);
}
} else if (module.position === 'end') {
$link.parent().append(' ', $tools);
} else {
$link.after(' ', $tools);
}
if (module.post) {
module.post($tools);
}
processed.add(this);
});
if (!isBatch) {
getWatched(titles);
}
};
let tools = [
{
name: 'edit',
url: t => t.getUrl({ action: 'edit' })
},
{
name: 'hist',
url: t => !t.missing && t.getUrl({ action: 'history' })
},
{
name: 'links',
url: t => mw.util.getUrl('Special:WhatLinksHere/' + t)
},
{
name: 'watch',
url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }),
callback: watchHandler
}
];
let addTool = ($tools, tool, escapedName) => {
let t = $tools.data('listtools');
let $duplicate = escapedName &&
$tools.children('.listtools-' + escapedName);
let url = tool.url;
if (typeof url === 'function') {
url = url(t);
if (!url) {
$duplicate?.remove();
return;
}
}
let $link = $('<a>').attr('href', url).text(getMsg(tool.name));
if (t.host) {
$link.prop('host', t.host);
}
if (tool.callback) {
$link.on('click', tool.callback);
}
let $wrapper = $('<span>').addClass('listtools-' + tool.name)
.append($link);
let $next = tool.next && $tools.children('.listtools-' + tool.next);
if ($next?.length) {
$duplicate?.remove();
$next.before($wrapper);
} else if ($duplicate?.length) {
$duplicate.replaceWith($wrapper);
} else {
$tools.append($wrapper);
}
};
let extend = tool => {
if (tool.label && !Object.hasOwn(messages, tool.label)) {
messages[tool.name] = tool.label;
}
if (tool.next) {
tool.next = $.escapeSelector(tool.next);
}
let existingTool = tools.find(t => t.name === tool.name);
if (existingTool) {
Object.assign(existingTool, tool);
} else {
tools.push(tool);
}
let escapedName = existingTool && $.escapeSelector(tool.name);
let $allTools = $('.listtools');
$allTools.each(function (i) {
addTool($allTools.eq(i), tool, escapedName);
});
};
let getWatched = async titles => {
if (!Array.isArray(titles)) {
titles = [...titles].slice(0, 500);
}
if (!titles.length) return;
(await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages.forEach(page => {
if (!page.watched) return;
$(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`)
.attr('href', mw.util.getUrl(page.title, { action: 'unwatch' }))
.text(getMsg('unwatch'));
});
getWatched(titles.slice(50));
};
mw.hook('listtools.ready').fire(extend);
let catTreeCallback = (records, observer) => {
let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a');
if ($links.length) {
observer.takeRecords();
observer.disconnect();
processLinks($links, catTreeModule);
}
};
let catTreeModule = {
selector: '.CategoryTreeItem > bdi > a',
types: [14, 'CategoryTree'],
position: 'end',
post: $tools => {
$tools.parent().next('.CategoryTreeChildren').each(function () {
new MutationObserver(catTreeCallback)
.observe(this, { childList: true });
});
}
};
let modules = [
{
selector: '#mw-pages li > a, #mw-pages li > span > a',
types: [14]
},
catTreeModule,
{
selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a',
types: [6]
},
{
selector: '#mw-globalusage-result a',
types: ['GlobalUsage']
},
{
selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a',
types: ['Search']
},
{
selector: '.mw-search-createlink a',
types: ['Search'],
titlesOnly: true
},
{
selector: '#watchlist-edit-form .cdx-table td > label > a',
types: ['EditWatchlist']
},
{
selector: '.plainlinks > li > a',
types: ['AbuseLog'],
titlesOnly: true
},
{
selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)',
types: ['Allmessages'],
position: 'end'
},
{
selector: '.mw-spcontent li a',
types: ['DisambiguationPageLinks', 'Listredirects'],
titlesOnly: true
},
{
selector: 'li > a:first-child',
types: ['FileDuplicateSearch']
},
{
selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a',
types: ['LintErrors'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: 'form > ul > li > a',
types: ['Nuke'],
position: 'end',
titlesOnly: true
},
{
selector: '.page-assessments a',
types: ['PageAssessments'],
titlesOnly: true
},
{
selector: '.TablePager_col_pr_page > a',
types: ['Protectedpages'],
position: 'end'
},
{
selector: '#mw-content-text > ul a',
types: ['Protectedtitles'],
position: 'end'
},
{
selector: '.mw-fr-pending-changes-page-title',
types: ['PendingChanges'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: '#mw-content-text > ul a:first-child',
types: ['StablePages'],
position: 'end'
},
{
selector: '.TablePager_col__page a',
types: ['TopicSubscriptions']
},
{
selector: '.undeleteResult > a',
types: ['Undelete'],
position: 'end',
useText: true
},
{
selector: '.TablePager_col_img_name > a:first-child',
// types: ['Listfiles'],
position: 'end'
},
{
selector: '.mw-newpages-pagename',
post: $tools => {
let $contents = $tools.parent().contents();
$contents.slice(
$contents.index($tools) + 1,
$contents.index($contents.filter('.mw-newpages-length'))
).replaceWith(' ');
}
},
{
selector: '#mw-whatlinkshere-list li > bdi > a'
},
{
selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)',
titlesOnly: true
},
{
selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a',
types: ['Log'],
titlesOnly: true
},
{
selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a',
types: ['ComparePages'],
position: 'end'
},
{
selector: '#movepage-oldlink, #movepage-newlink',
types: ['Movepage']
},
{
selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)',
types: ['Undelete'],
useText: true
},
{
selector: '.galleryfilename, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
}());
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// }).then(response => {
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// }).always(() => {
// $wrapper.html($link);
// });
// }
// });
extend({
name: 'copy',
url: '#',
callback: function (e) {
e.preventDefault();
let text = $(this).closest('.listtools').data('listtools').toText();
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch (err) {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
}
});
});
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' &&
(function consecudiff() {
mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}');
let isHist = mw.config.get('wgAction') === 'history';
class Consecudiff {
constructor(lis, isContribs) {
this.isContribs = isContribs;
this.isEnhanced = !isHist && !isContribs &&
lis[0].classList.contains('mw-enhanced-rc');
this.threshold = isContribs ? window.consecudiffContribsThreshold || 120
: isHist ? window.consecudiffHistThreshold || 720
: window.consecudiffThreshold || 720;
this.strictMode = !isContribs &&
!!window.consecudiffDetectInterruptions;
this.diffSelector = isHist
? 'a.mw-history-histlinks-previous'
: '.mw-changeslist-diff';
this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' ||
(isHist || isContribs) && 'a.mw-changeslist-date';
this.hybridSelector = this.diffSelector;
if (this.permaSelector) {
this.hybridSelector += ', ' + this.permaSelector;
}
this.topClass = isContribs
? 'mw-contributions-current'
: 'mw-changeslist-last';
let dependencies = ['mediawiki.util'];
if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') {
dependencies.push('mediawiki.language.months');
}
mw.loader.using(dependencies, () => {
let chunks;
if (isHist) {
chunks = this.chunkByUser(lis);
} else {
chunks = [];
this.groupByTitle(lis).forEach(group => {
chunks.push(...this.chunkByUser(group));
});
}
let subchunks = [];
chunks.forEach(chunk => {
subchunks.push(...this.divideByDate(chunk));
});
let linkPairs = [];
subchunks.forEach(subchunk => {
linkPairs.push(...this.makeLinks(subchunk));
});
linkPairs.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
});
}
groupByTitle(lis) {
let selector = this.isContribs
? '.mw-contributions-title'
: '.mw-changeslist-title';
let lisByTitle = {};
lis.forEach(li => {
let link = (this.isEnhanced ? li.closest('table') : li)
.querySelector(selector);
if (!link) return;
let title = link.textContent;
if (!lisByTitle.hasOwnProperty(title)) {
lisByTitle[title] = [];
}
lisByTitle[title].push(li);
});
return Object.values(lisByTitle).filter(group => group.length > 1);
}
chunkByUser(lis) {
if (this.isSingleContribs) {
return [lis];
}
let chunks = [], lastSplitAt = 0, prevUser;
this.isSingleContribs = lis.some((li, i) => {
let link = li.querySelector('.mw-userlink');
if (!link && this.isContribs) {
return true;
}
let user = link && link.textContent;
if (!link || i && user !== prevUser) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevUser = user;
});
if (this.isSingleContribs) {
return [lis];
}
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
divideByDate(lis) {
let chunks = [], lastSplitAt = 0, prevDate;
lis.forEach((li, i) => {
let date;
if (isHist || this.isContribs) {
date = this.parseDate(
li.querySelector('.mw-changeslist-date').textContent
);
} else {
date = Date.parse(
li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z')
);
}
if (date) {
date = date / 60000;
}
if (i && prevDate - date > this.threshold) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevDate = date;
if (!this.strictMode || lastSplitAt === i) return;
let prevDiff = lis[i - 1].querySelector(this.diffSelector);
if (prevDiff) {
let prevNext = mw.util.getParamValue('oldid', prevDiff.search);
if (prevNext !== li.dataset.mwRevid) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
}
});
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
makeLinks(lis) {
let count = lis.length;
let firstPerma;
let start = lis.findIndex(li => (
firstPerma = li.querySelector(this.hybridSelector)
));
if (start === -1 || count - start < 2) return [];
let end, lastDiff;
for (let i = count - 1; i > start; i--) {
if (!isHist && !this.isContribs) {
lastDiff = lis[i].querySelector(this.diffSelector);
if (lastDiff ||
lis[i].classList.contains('mw-changeslist-src-mw-new')
) {
end = i + 1;
break;
}
}
if (this.permaSelector && lis[i].querySelector(this.permaSelector)) {
end = i + 1;
break;
}
}
if (!end) return [];
count = end - start;
let params = { diff: lis[start].dataset.mwRevid };
if (lastDiff) {
params.oldid = mw.util.getParamValue('oldid', lastDiff.search);
} else {
params.oldid = lis[end - 1].dataset.mwRevid;
if (isHist && lis[end - 1].querySelector(this.diffSelector) ||
this.isContribs && !lis[end - 1].querySelector('.newpage')
) {
params.direction = 'prev';
}
}
let title = !isHist && mw.util.getParamValue('title', firstPerma.search);
let url = mw.util.getUrl(title, params);
let classes = 'consecudiff';
if (!isHist && lis[start].classList.contains(this.topClass)) {
classes += ' consecudiff-top';
}
return lis.slice(start, end).map((li, i) => [
$('<span>').addClass(classes).append(
$('<a>')
.attr('href', url)
.text(this.convertNumber(count - i + '/' + count))
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
parseDate(s) {
let date = Date.parse(s);
if (date) {
return date;
}
if (s.includes(',')) date = Date.parse(s.replace(',', ''));
if (date) {
return date;
}
if (mw.loader.getState('mediawiki.language.months') !== 'ready') return;
s = s.replace(/\D/g, c => {
let n = mw.language.convertNumber(c, true);
return Number.isNaN(n) ? c : n;
});
let h, m;
s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => {
h = $1;
m = $2;
return ' ';
});
if (!h) return;
let y, dateFirst;
s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => {
y = $2;
dateFirst = /\d/.test($1);
return $1 + ' ';
});
if (!y) return;
let mo, d;
if (dateFirst) {
[d, s] = this.getDate(s);
if (!d) return;
[mo, s] = this.getMonth(s);
if (mo === -1) return;
} else {
[mo, s] = this.getMonth(s);
if (mo === -1) return;
[d, s] = this.getDate(s);
if (!d) return;
}
return new Date(y, mo, d, h, m).getTime();
}
getMonth(s) {
if (!this.months) {
this.months = mw.language.months.abbrev
.concat(mw.language.months.names, mw.language.months.genitive)
.reverse();
}
let mo = this.months.findIndex(mn => {
let temp = s.replace(mn, ' ');
if (temp !== s) {
s = temp;
return true;
}
});
if (mo === -1) {
let [numeric, temp] = this.getDate(s);
numeric = parseInt(numeric);
if (numeric > 0 && numeric < 13) {
mo = numeric - 1;
s = temp;
}
} else {
mo = 11 - mo % 12;
}
return [mo, s];
}
getDate(s) {
let d;
s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => {
d = $2;
return $1 + ' ';
});
return [d, s];
}
convertNumber(num) {
try {
return mw.language.convertNumber(num);
} catch (e) {
return num;
}
}
}
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-body').each(function () {
let lis = this.querySelectorAll('.mw-contributions-list > li');
if (lis.length > 1) {
new Consecudiff([...lis], !isHist);
}
});
if (isHist) return;
let $lists = $content.filter('.mw-changeslist');
if (!$lists.length) {
$lists = $content.find('.mw-changeslist');
}
$lists.each(function () {
let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]');
if (lis.length > 1) {
new Consecudiff([...lis]);
}
});
});
}());
if (mw.config.get('wgNamespaceNumber') === 14 && (
mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId')
)) {
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter',
'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement',
'mediawiki.interface.helpers.styles', 'user.options'
]);
}
$(function moveHistory() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.moveHistoryDialog) {
window.moveHistoryDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter',
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions',
'mediawiki.interface.helpers.styles'
]);
});
});
});
$(function sectionSearch() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.sectionSearchDialog) {
window.sectionSearchDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows',
'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget'
]);
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' &&
mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() {
mw.hook('wikipage.content').add($content => {
let $table = $content.find('.mw-centralauth-wikislist').has('td');
if (!$table.length) return;
$table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
[10, 828].includes(mw.config.get('wgNamespaceNumber')) &&
!mw.config.get('wgTitle').endsWith('/doc') &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// $(function templatePreviewGuard() {
// let button = document.querySelector('input[name="wpTemplateSandboxPreview"]');
// if (!button) return;
// let proceed;
// button.addEventListener('click', e => {
// if (proceed) {
// proceed = false;
// return;
// }
// e.preventDefault();
// e.stopPropagation();
// let formData = new FormData(button.form);
// let page = formData.get('wpTemplateSandboxPage');
// let temp = formData.get('wpTemplateSandboxTemplate');
// if (!page || !temp) return;
// mw.loader.using('mediawiki.api').then(() => (
// new mw.Api().get({
// action: 'query',
// titles: page,
// prop: 'templates',
// tltemplates: temp,
// formatversion: 2
// })
// )).always(response => {
// if (((((response || {}).query || {}).pages || [])[0] || {}).templates ||
// confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`)
// ) {
// proceed = true;
// button.click();
// }
// });
// }, true);
// if (!mw.config.get('wgArticleId')) return;
// let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget');
// if (!widgetEl) return;
// let pn = mw.config.get('wgPageName').replace(/_/g, ' ');
// mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => (
// new mw.Api().get({
// action: 'query',
// titles: pn,
// prop: 'transcludedin',
// tiprop: 'title',
// tilimit: 'max',
// formatversion: 2
// })
// )).then(response => {
// if (!response.batchcomplete) return;
// let pages = response.query.pages[0].transcludedin
// .filter(o => o.title !== pn);
// if (!pages.length) return;
// let widget = OO.ui.infuse(widgetEl);
// if (pages.length === 1) {
// widget.setValue(pages[0].title);
// return;
// }
// widget.$element.replaceWith(
// new OO.ui.ComboBoxInputWidget({
// id: 'wpTemplateSandboxPage',
// maxlength: widget.$input.prop('maxLength'),
// name: widget.$input.prop('name'),
// options: pages
// .sort((a, b) => a.ns - b.ns || -(a.title < b.title))
// .map(o => ({ data: o.title })),
// placeholder: widget.$input.prop('placeholder'),
// tabIndex: widget.getTabIndex(),
// value: widget.getValue()
// }).on('enter', e => {
// e.preventDefault();
// button.click();
// }).$element
// );
// });
// });
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoSectionLink.js&action=raw&ctype=text/javascript', 's');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(function copyRevId() {
let handler = function (e) {
e.preventDefault();
let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid ||
this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!text) return;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
document.execCommand('copy');
$input.remove();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id')
)
);
});
}());
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
let handler = async function (e) {
e.preventDefault();
let td = this.closest('.diff td');
let rev = td
? td.querySelector('[data-mw-revid]')?.dataset.mwRevid
: this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!rev) {
mw.notify(`Couldn't get the revision.`, {
tag: 'markasunseen',
type: 'error'
});
return;
}
let pn = td
? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title')
: mw.config.get('wgPageName');
if (!pn) return;
await mw.loader.using('mediawiki.api');
let result = (await new mw.Api().postWithEditToken({
action: 'setnotificationtimestamp',
[td ? 'newerthanrevid' : 'torevid']: rev,
titles: pn,
formatversion: 2
})).setnotificationtimestamp?.[0];
if (Object.hasOwn(result, 'notificationtimestamp')) {
mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'success'
});
} else if (result?.notwatched) {
mw.notify('This page is not on your watchlist.', {
tag: 'markasunseen',
type: 'warn'
});
} else {
mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'error'
});
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions after this one as unseen'
}).on('click', handler).text('unseen'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions since this one as unseen'
}).on('click', handler).text('unseen')
)
);
});
})();
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) ||
(mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) &&
mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() {
let handler = function (e) {
e.preventDefault();
let parent = this.closest('li, td');
let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0];
if (!ts) return;
let user = parent.querySelector('.mw-userlink').textContent;
if (mw.util.isIPv6Address(user)) {
user = user.toUpperCase();
}
let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned';
let text = `{{subst:${temp}|${user}|${ts}}}`;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () {
if (mw.config.get('wgWikiID') === 'metawiki') {
return true;
}
let link = this.querySelector('strong > a') ||
this.parentElement.querySelector('#differences-prevlink, #differences-nextlink');
if (!link) return;
let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search));
return t.isTalkPage() || t.namespace === 4;
}).append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig')
)
);
});
});
// mw.config.get('wgAction') === 'history' &&
// mw.loader.using('mediawiki.util', function () {
// mw.hook('wikipage.content').add($content => {
// $content.find('a.mw-changeslist-date').after(function () {
// return [
// ' (',
// $('<a>').attr('href', mw.util.getUrl(null, {
// action: 'edit',
// oldid: this.closest('li').dataset.mwRevid
// })).text('e'),
// ')'
// ];
// });
// });
// });
// ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
// mw.hook('wikipage.content').add($content => {
// $content.find('.mw-changeslist-history').parent().after(function () {
// return $('<span>').append(
// $('<a>').attr(
// 'href',
// this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit'
// ).text('e')
// );
// });
// });
if (screen.width < 500) {
mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}');
location.hash && $(() => {
let target = document.querySelector(':target');
if (target?.getBoundingClientRect().top < 0) {
target.scrollIntoView();
}
});
}
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(mw.config.exists('wgCodeEditorCurrentLanguage') ||
mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') &&
(function saveNEdit() {
let notif;
$(document.body).on('click', '#wpSave', async function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
e.originalEvent?.defaultPrevented
) {
return;
}
e.preventDefault();
await mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core'
]);
let button = OO.ui.infuse(this.parentElement).setDisabled(true);
let $textarea = $('#wpTextbox1');
let text = $textarea.textSelection('getContents');
let $summary = $('#wpSummary');
let formData = new FormData(this.form);
let promise = new mw.Api().postWithEditToken({
action: 'edit',
title: mw.config.get('wgPageName'),
text: text,
section: formData.get('wpSection') || undefined,
summary: $summary.textSelection('getContents'),
[$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1,
baserevid: formData.get('editRevId'),
basetimestamp: formData.get('wpEdittime'),
starttimestamp: formData.get('wpStarttime'),
watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch',
watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined,
undo: formData.get('wpUndidRevision') || undefined,
undoafter: formData.get('wpUndoAfter') || undefined,
contentformat: formData.get('format'),
contentmodel: formData.get('model'),
assertuser: mw.config.get('wgUserName'),
formatversion: 2
});
notif?.close();
notif = null;
try {
let response = await promise;
if (response?.edit?.result !== 'Success') throw '';
$('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove();
$textarea.data('origtext', text).prop('defaultValue', text);
$summary.val($summary.prop('defaultValue'));
if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') {
let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js'];
storage.deleteData(mw.config.get('wgPageName'));
storage.closeDatabase();
}
notif = await mw.notify(response.edit.nochange ? 'No change' : [
document.createTextNode('Saved'),
$('<p>').append(
new OO.ui.ButtonWidget({
href: mw.util.getUrl(),
target: '_blank',
label: 'View'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, {
diff: response.edit.newrevid || 'cur',
diffonly: 1
}),
target: '_blank',
label: 'Diff'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, { action: 'history' }),
target: '_blank',
label: 'History'
}).$element
)[0]
], { tag: 'savenedit' });
} catch (error) {
notif = await mw.notify(error?.error?.info || error || 'Save failed', {
autoHideSeconds: 'long',
tag: 'savenedit',
type: 'error'
});
} finally {
button.setDisabled();
}
});
}());
mw.config.get('wgNamespaceNumber') === 0 &&
mw.config.get('wgAction') === 'view' &&
mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) &&
$(() => {
let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, ''));
let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`);
if (!$links.length) return;
let titles = {};
$links = $links.filter(function () {
let text = this.textContent;
return !(titles[text] = Object.hasOwn(titles, text));
});
mw.notify(
$links.length === 1
? $links.clone()
: $('<ul>').append($links.clone().wrap('<li>').parent()),
{ autoHideSeconds: 'long' }
);
});
['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
$.when($.ready, mw.loader.using([
'user.options', 'mediawiki.util', 'mediawiki.api'
])).then(function rcMuter() {
let os = mw.user.options.get('userjs-rcmuter');
let set = new Set(os && os.split('|'));
let save = () => {
let ns = [...set].join('|');
if (ns === mw.user.options.get('userjs-rcmuter')) return;
new mw.Api().saveOption('userjs-rcmuter', ns);
mw.user.options.set('userjs-rcmuter', ns);
$edit.attr('data-rcmuter', set.size);
};
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
'data-rcmuter': set.size
}).text('Edit muted').on('click', e => {
e.preventDefault();
mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]).then(() => OO.ui.getWindowManager().getWindow('message')).then(dialog => {
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...set]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
let instance = dialog.open({
message: $([
document.createTextNode('Muted users:'),
multiselect.$element[0]
]),
size: 'medium'
});
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().menu.toggle(false);
});
});
instance.closed.then(result => {
if (!result || result.action !== 'accept') return;
set = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
});
let buttonsShown;
let $toggleButtons = $('<a>').attr('href', '#').text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (buttonsShown) {
mw.hook('wikipage.content').remove(addButtons);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addButtons);
this.textContent = 'Hide toggle buttons';
}
buttonsShown = !buttonsShown;
});
let $toggle = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleSpan = $('<span>').hide().append($toggle);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleButtons),
$toggleSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-userlink ~ .mw-usertoollinks, .mw-changeslist-line-inner-userLink ~ .mw-changeslist-line-inner-userTalkLink')
.prevAll('.mw-userlink, .mw-changeslist-line-inner-userLink')
.last().text().trim();
if (!user) {
mw.notify(`Can't retrieve the username.`, {
tag: 'rcmuter',
type: 'error'
});
return;
}
let muting = this.parentElement.classList.toggle('rcmuter-unmute');
set[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addButtons = $content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) {
$content = $('#mw-content-text');
if ($content.has('.rcmuter-toggle').length) return;
}
let $tools = $content.find('.mw-usertoollinks.mw-changeslist-links');
let $muted = $tools.filter('.rcmuter-muted *');
$tools.not($muted).append(
$('<span>').addClass('rcmuter-toggle').append(
$('<a>').attr('href', '#').text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr('href', '#').text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let muted = set.has(this.textContent);
if (muted) mutedCount++;
return muted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!set.size) {
$toggleSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-userlink:only-child')
.filter(filter).closest('table').addClass('rcmuter-muted');
$content.find('.mw-userlink:not(.changedby > *, .comment *, .rcmuter-muted *)')
.filter(filter).closest('.mw-changeslist-line, table').addClass('rcmuter-muted')
.closest('table.mw-enhanced-rc').find('.changedby > .mw-userlink').filter(filter).addClass('rcmuter-muted');
$toggleSpan.toggle(!!mutedCount);
$toggle.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() {
if (!document.getElementById('p-tb')) return;
let messages = Object.assign({
portlet: 'RefRenamer',
loading: 'Loading RefRenamer...'
}, window.refrenamerMessages);
let clicked;
mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.refRenamer) {
window.refRenamer();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
if (['edit', 'submit'].includes(mw.config.get('wgAction'))) {
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's');
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's');
}
mw.config.get('wgAction') !== 'history' &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's');
mw.config.exists('wgDiffNewId') &&
mw.config.get('wgDiscussionToolsFeaturesEnabled') &&
(function () {
let data = {}, clickHandler, autoClear, run;
window.dtc = data;
let highlight = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js']
.highlightNewComments(mw.dt.pageThreads, true, ids);
if (clickHandler) {
$(document.body).off('click', clickHandler);
return;
}
$._data(document.body, 'events').click.some(o => {
if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) {
$(document.body).off('click', o.handler);
clickHandler = o.handler;
return true;
}
});
$._data(window, 'events').popstate.some(o => {
if (String(o.handler).includes('highlighter.highlightTargetComment(')) {
$(window).off('popstate', o.handler);
return true;
}
});
};
let scroll = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
let yToSpan = Object.fromEntries(
ids.map(id => document.getElementById(id)).filter(Boolean)
.map(span => [span.getBoundingClientRect().y, span])
);
let ys = Object.keys(yToSpan);
if (!ys.length) return;
let lower = ys.filter(y => y > 10);
if (!lower.length ||
Math.max(...lower) < document.documentElement.clientHeight
) {
yToSpan[Math.min(...ys)].scrollIntoView();
} else {
yToSpan[Math.min(...lower)].scrollIntoView();
}
};
let scrollToNext = function (e) {
e.preventDefault();
let revId = mw.config.get('wgDiffOldId');
if (!revId || !data[revId]) return;
let i = data[revId].indexOf(
this.closest('[data-mw-thread-id]').dataset.mwThreadId
);
if (i === -1) return;
let next = data[revId][i + 1] || data[revId][0];
document.getElementById(next).scrollIntoView();
};
mw.hook('wikipage.content').add(async $content => {
let revId = mw.config.get('wgDiffOldId');
if (!revId) return;
let param = new URLSearchParams(location.search).get('diffonly');
if (param && param !== '0') return;
if (data[revId]) {
highlight(revId);
return;
}
await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']);
let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp'));
data[revId] = mw.dt.pageThreads.getCommentItems()
.filter(c => c.timestamp > begin).map(c => c.id);
if (!data[revId].length) return;
await new Promise(setTimeout);
highlight(revId);
$content.find('.ext-discussiontools-init-replylink-buttons').filter(function () {
return data[revId].includes(this.dataset.mwThreadId);
}).children('span:last-of-type').before(
' | ',
$('<a>').attr({
href: '#',
role: 'button'
}).text('next').on('click', scrollToNext)
);
if (run || !document.getElementById('p-tb')) return;
run = true;
let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext');
portlet.firstElementChild.addEventListener('click', e => {
e.preventDefault();
scroll(mw.config.get('wgDiffOldId'));
});
mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => {
e.preventDefault();
autoClear = !autoClear;
if (autoClear) {
$(document.body).on('click', clickHandler)[0].click();
} else {
highlight(mw.config.get('wgDiffOldId'));
}
});
mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`);
});
}());
mw.config.get('wgNamespaceNumber') === 6 &&
mw.config.get('wgAction') === 'view' &&
mw.hook('wikipage.content').add($content => {
$content.find('.filehistory .mw-usertoollinks-contribs').after(function () {
return [
' | ',
$('<a>').attr('href', `${
mw.config.get('wgScript')
}?title=Special:ListFiles/${
this.pathname.replace(/^.+\//, '')
}&ilshowall=1`).text('uploads')
];
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(function () {
if (!$('input[name="wpSection"]').val()) return;
mw.hook('wikipage.content').add(async $content => {
let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text');
if (!$refs.length) return;
let ids = {};
$refs.each(function () {
ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this;
});
let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`);
$($.parseHTML(response)).find('.mw-reference-text').each(function () {
ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this);
});
});
});
mw.hook('moremenu.ready').add(config => {
$('#mm-page-purge-cache > a').on('click', e => {
e.preventDefault();
new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
}).then(() => {
location.href = mw.util.getUrl();
});
});
$('#mm-page-search-search-history-wikiblame > a').on('click', function (e) {
e.preventDefault();
let q = prompt();
if (q === null) return;
let href = this.href;
if (q) {
let removal = q[0] === '!';
if (removal) {
q = q.slice(1);
}
href += '&needle=' + encodeURIComponent(q);
if (removal) {
href += '&binary_search_inverse=on';
}
href += '&force_wikitags=on';
}
open(href, '_blank');
});
$('#mm-page-expand-templates > a').on('click auxclick', function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val());
let url = revId
? '/w/rest.php/v1/revision/' + revId
: '/w/rest.php/v1/page/' + config.page.encodedName;
$.get(url).then(response => {
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', response.source],
['wpContextTitle', config.page.name],
['wpRemoveComments', 1]
].map(([n, v]) => $('<input>').attr({
name: n,
type: 'hidden'
}).val(v))
).appendTo(document.body).trigger('submit').remove();
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' &&
mw.hook('apisandbox.formatRequest').add((...args) => {
args[4].complete = function () {
setTimeout(() => {
mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active'));
}, 100);
};
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
let section = $('input[name="wpSection"]').val();
if (section) {
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let save = () => {
let newSource = $textarea.textSelection('getContents');
if (newSource === source) {
mw.storage.session.remove('editfullpage');
} else {
mw.storage.session.setObject('editfullpage', [
mw.config.get('wgPageName'),
section,
newSource.trimEnd(),
infuseAndCall('#wpSummaryWidget', 'getValue') || '',
Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0,
Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0,
infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite'
]);
}
};
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
setInterval(() => {
mw.requestIdleCallback(save);
}, 3000);
window.addEventListener('beforeunload', save);
return;
}
let data = mw.storage.session.getObject('editfullpage');
mw.storage.session.remove('editfullpage');
console.log(data);
if (!data || data[0] !== mw.config.get('wgPageName')) return;
let isNew = data[1] === 'new';
let isLead = data[1] === '0';
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' };
let orig = [];
if (isNew) {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n';
start = source.length;
} else {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']);
let { parse } = await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'sections',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1,
formatversion: 2
});
let target = !isLead && parse.sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = parse.sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n');
start = isLead ? 0 : target.byteoffset;
} else {
newSource = source + '\n\n' + data[2] + '\n';
start = source.length;
msg = `Section restored. Couldn't find the section. The source is appended at bottom.`;
notifOpts.type = 'warn';
}
orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue');
infuseAndCall('#wpSummaryWidget', 'setValue', data[3]);
}
$textarea.textSelection('setContents', newSource);
orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected');
infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]);
orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected');
infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]);
orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue');
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]);
setTimeout(() => {
$textarea.textSelection('setSelection', { start });
});
let notif = await mw.notify($([
document.createTextNode(msg || 'Section restored.'),
$('<p>').append(
new OO.ui.ButtonWidget({
flags: 'destructive',
label: 'Discard'
}).on('click', () => {
$textarea.textSelection('setContents', source);
if (orig[0]) {
infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]);
}
infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]);
infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]);
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]);
notif.close();
}).$element
)[0]
]), notifOpts);
});
mw.config.exists('wgPostEdit') &&
mw.loader.using('mediawiki.storage', () => {
mw.storage.session.remove('editfullpage');
});
mw.config.get('wgAction') === 'history' &&
mw.hook('wikipage.content').add(async $content => {
if (!$content.has('.mw-history-line-updated').length) return;
let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href');
if (!href) {
await mw.loader.using(['mediawiki.api', 'mediawiki.util']);
let page = (await new mw.Api().get({
action: 'query',
titles: mw.config.get('wgPageName'),
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev >= page.lastrevid) return;
href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
}
$content.find('.mw-history-compareselectedversions-button').first().after(
' ',
$('<a>').attr({
class: 'unseendiff',
href: href
}).text('unseen')
);
});
(async () => {
let cspn = mw.config.get('wgCanonicalSpecialPageName');
let isBp = cspn === 'Blankpage';
if (!isBp && cspn !== 'Watchlist') return;
await mw.loader.using('mediawiki.util');
let notify = async (text, options, pn) => {
let msg = [document.createTextNode(text)];
if (pn) {
msg.push(
$('<p>').append(
$('<a>').attr('href', mw.util.getUrl(pn)).text(pn),
' ',
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'edit' }))
.text('edit')
),
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'history' }))
.text('history')
)
)
)[0]
);
}
if (isBp) {
await $.ready;
$('#mw-content-text').html(msg);
} else {
return mw.notify(msg, Object.assign(options || {}, {
tag: 'unseendiff'
}));
}
};
let getUrl = async pn => {
await mw.loader.using('mediawiki.api');
let page = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
if (!page.notificationtimestamp) {
notify(`Couldn't get the last seen time.`, {
type: 'warn'
}, pn);
return;
}
let rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (rev === page.lastrevid) {
notify('Already seen.', {
type: 'warn'
}, pn);
return;
}
if (!rev) {
rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
rvdir: 'newer',
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev) {
notify(`Couldn't get the last seen revision.`, {
type: 'warn'
}, pn);
return;
}
}
if (rev > page.lastrevid) {
notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, {
autoHideSeconds: 'long',
type: 'warn'
}, pn);
return;
}
return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
};
if (isBp) {
let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1];
if (!pn) return;
notify('Loading...', null, pn);
let href = await getUrl(pn);
if (!href) return;
notify('Redirecting...', null, pn);
location.href = href;
return;
}
let handler = async function (e) {
if (e.which > 2) return;
e.preventDefault();
let pn = this.dataset.pn;
if (!pn) {
notify(`Couldn't get the page name.`, {
type: 'error'
});
return;
}
let notifPromise = notify('Loading...', {
autoHideSeconds: 'long'
});
let href = await getUrl(pn);
if (!href) return;
$(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({
class: 'unseendiff',
href: href,
target: '_blank'
}).off('click auxclick', handler);
if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) {
open(href);
} else {
this.click();
}
(await notifPromise).close();
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner'
).each(function () {
let pn = this.dataset.targetPage ||
this.closest('[data-target-page]')?.dataset.targetPage ||
this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage;
if (!pn) return;
$('<span>').append(
$('<a>').attr({
class: 'unseendiff-loader',
href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`),
'data-pn': pn
}).on('click auxclick', handler).text('unseen')
).appendTo(
[...this.querySelectorAll('.mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
});
})();
['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
mw.loader.using('mediawiki.util', () => {
let watched = new Set();
let query = async lis => {
let titles = Object.keys(lis).slice(0, 50);
if (!titles.length) return;
await mw.loader.using('mediawiki.api');
let pages = (await new mw.Api().post({
action: 'query',
titles: titles,
prop: 'info',
inprop: 'notificationtimestamp|watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages;
for (let page of pages) {
if (!Object.hasOwn(lis, page.title)) continue;
if (page.watched) {
watched.add(page);
$(lis[page.title]).addClass('watched');
}
if (!page.notificationtimestamp) continue;
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev === page.lastrevid) continue;
if (rev > page.lastrevid) {
mw.notify($([
document.createTextNode('Invalid rev for "'),
$('<a>').attr({
href: mw.util.getUrl(page.title, { action: 'history' }),
target: '_blank'
}).text(page.title)[0],
document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`),
]), {
autoHideSeconds: 'long',
type: 'warn'
});
continue;
}
$('<span>').append(
$('<a>').attr({
class: 'unseendiff',
href: mw.util.getUrl(page.title, {
diff: page.lastrevid,
oldid: rev
})
}).text('unseen')
).appendTo(
lis[page.title].map(li => (
[...li.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ')
))
);
}
titles.forEach(title => {
delete lis[title];
});
query(lis);
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]'
).each(function () {
let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history');
let pn = link ? new URLSearchParams(link.search).get('title') : '';
$('<span>').append(
$('<a>').attr({
class: 'mw-changeslist-diff',
href: mw.util.getUrl(pn, {
diff: 'cur',
oldid: this.dataset.mwRevid
})
}).text('cur')
).appendTo(
[...this.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
if (mw.config.get('wgWikiID') === 'wikidatawiki') return;
let lis = {};
$content.find('.mw-contributions-title').each(function () {
let title = this.textContent;
if (!Object.hasOwn(lis, title)) {
lis[title] = [];
}
lis[title].push(this.closest('li'));
});
Object.keys(lis).forEach(title => {
if (watched.has(title)) {
$(lis[title]).addClass('watched');
delete lis[title];
}
});
query(lis);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript');
mw.config.get('wgWikiID') === 'metawiki' &&
(async () => {
let css = mw.loader.addStyleTag(`.wishtitle {
font-size: 90%;
font-style: italic;
word-break: break-word;
}
.wishtitle > a {
color: var(--color-warning, #886425);
}
.wishtitle > a:visited {
color: var(--border-color-warning--hover, #735421);
}
.wishtitle-declined > a {
text-decoration: line-through;
}
.wishtitle-declined > a:hover,
.wishtitle-declined > a:focus,
.mw-underline-always .wishtitle-declined > a {
text-decoration: line-through underline;
}
#watchlist-edit-form .wishtitle {
display: inline-block;
}
.mw-search-result-heading > .wishtitle,
.catchangesviewer-table .wishtitle {
display: block;
}
.catchangesviewer-table:has(.wishtitle) {
white-space: wrap;
}`);
let lang = mw.config.get('wgUserLanguage');
let titles;
let loadTitles = async () => {
await mw.loader.using('mediawiki.storage');
titles = titles || mw.storage.getObject('wishtitles');
if (titles?.lang !== lang) {
titles = { lang, w: [], fa: [] };
}
};
let updateTitles = async (crwstatuses, crwcontinue) => {
await mw.loader.using('mediawiki.api');
let params = {
action: 'query',
list: 'communityrequests-wishes',
crwlang: lang,
crwstatuses: crwstatuses,
crwprop: 'title|updated',
crwsort: 'updated',
crwdir: 'ascending',
crwlimit: 'max',
crwcontinue: crwcontinue,
formatversion: 2
};
if (!crwcontinue && !crwstatuses && titles._) {
params.crwcontinue = `|${titles._}|0`;
}
let response = await new mw.Api().get(params);
let wishes = response?.query?.['communityrequests-wishes'];
if (wishes?.length) {
let $span = $('<span>');
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = $span.html(w.title).text();
if (crwstatuses === 'declined') {
(titles.wd = titles.wd || []).push(id - 1);
}
let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1];
if (!faId) return;
titles.fa[faId - 1] = w.focusareatitle;
});
if (!crwstatuses) {
titles._ = wishes.at(-1).updated.replace(/\D/g, '');
}
}
let expiry = 86400;
if (crwstatuses || crwcontinue) {
let prev = mw.storage.getObject('_EXPIRY_wishtitles');
if (prev) {
expiry = Math.round(Date.now() / 1000) + 86400 - prev;
}
}
mw.storage.setObject('wishtitles', titles, expiry);
crwcontinue = response?.continue?.crwcontinue;
if (crwcontinue) {
await updateTitles(crwstatuses, crwcontinue);
}
};
let getTitle = id => (
id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await $.get(
`https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html`
)));
await queryTitles(xfd, xfd.titleExtractor($page));
};
let queryCat = async (xfd, gcmcontinue) => {
let response = await new mw.Api().get({
action: 'query',
prop: 'info|categories',
inprop: 'watched',
clprop: 'sortkey',
clcategories: `Category:${xfd.cat}`,
generator: 'categorymembers',
gcmtitle: `Category:${xfd.cat}`,
gcmlimit: 'max',
gcmsort: 'timestamp',
gcmdir: 'older',
gcmcontinue: gcmcontinue,
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') {
xfd.pages.push(p.title);
}
});
if (response?.continue?.gcmcontinue) {
await queryCat(xfd, response.continue.gcmcontinue);
}
};
let show = async (xfd, lastId, isCache) => {
if (xfd.portlet && isCache) return;
let portletId = 'p-xfdnotifier-' + xfd.id;
if (xfd.portlet) {
$(xfd.portlet).find('ul').empty();
if (!xfd.pages.length) return;
} else {
await $.ready;
xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId);
}
let $label = $(`#${portletId}-label`).attr('title', xfd.full);
if (xfd.page) {
$label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page)));
}
xfd.pages.forEach(p => {
let t = mw.Title.newFromText(p);
let isTalk = t.isTalkPage();
let $other = $('<a>').attr({
href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(),
title: isTalk ? 'subject' : 'talk'
}).text(isTalk ? 's' : 't');
let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a');
$('<span>').addClass('xfdnotifier-sublinks').append(
$('<span>').append($other),
$('<span>').append(
$('<a>').attr({
href: t.getUrl({ action: 'history' }),
title: 'history'
}).text('h')
)
).insertAfter(link);
});
};
mw.hook('wikipage.content').add(mw.util.throttle(async () => {
let cache = mw.storage.getObject('xfdnotifier') || {};
let lastId = 'p-tb';
for (let xfd of xfds) {
let portletId = 'p-xfdnotifier-' + xfd.id;
let now = Math.floor(Date.now() / 1000);
if (now - cache[xfd.id]?.[0] < 600) {
xfd.pages = cache[xfd.id].slice(1);
await show(xfd, lastId, true);
lastId = portletId;
continue;
}
xfd.pages = [];
if (xfd.cat) {
await queryCat(xfd);
} else if (xfd.page) {
await queryPage(xfd);
}
cache[xfd.id] = [now, ...xfd.pages];
mw.storage.setObject('xfdnotifier', cache, 604800);
await show(xfd, lastId);
lastId = portletId;
}
}, 1800000));
})();
tnrvss1jybenatqybf1a8jq9yhyxa2p
735547
735546
2026-03-29T13:47:26Z
Nardog
40946
735547
javascript
text/javascript
(async function listTools() {
let pageAction = mw.config.get('wgAction');
let isView = pageAction === 'view';
let isEdit = ['edit', 'submit'].includes(pageAction);
if (!isView && !isEdit) return;
let pageType = mw.config.get('wgCanonicalSpecialPageName') ||
mw.config.get('wgNamespaceNumber');
if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') &&
!mw.config.get('wgIsRedirect') &&
!mw.config.get('wgPageName').includes('/')
) {
return;
}
await mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api',
'mediawiki.interface.helpers.styles'
]);
mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) {
font-size: 85%;
}
.listtools, .listtools a {
font-weight: normal !important;
font-style: normal;
}
.mw-datatable .listtools {
display: block;
}
.listtools + .mw-whatlinkshere-tools,
#watchlist-edit-form .listtools ~ .mw-changeslist-links,
.mw-special-DisambiguationPageLinks .listtools + a {
display: none;
}`);
let messages = Object.assign({
watched: 'Added "$1" to your watchlist',
watchFail: `Couldn't watch "$1"`,
unwatchFail: `Couldn't unwatch "$1"`
}, window.listtoolsMessages);
let getMsg = (key, ...args) => (
Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key
);
let notif;
let watchHandler = async function (e) {
e.preventDefault();
let $link = $(this);
let $wrapper = $link.parent();
$link.detach();
let params = new URLSearchParams(this.search);
let action = params.get('action');
$wrapper.text(getMsg(action + 'ing'));
let pn = params.get('title').replaceAll('_', ' ');
let promise = new mw.Api()[action](pn);
if (notif) {
notif.close();
notif = null;
}
try {
let result = await promise;
if (!result || !result[action + 'ed']) throw '';
let newAction = action === 'watch' ? 'unwatch' : 'watch';
params.set('action', newAction);
$link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`)
.attr('href', this.pathname + '?' + params)
.text(getMsg(newAction));
if (action !== 'watch') return;
let require = await mw.loader.using([
'mediawiki.notification', 'mediawiki.watchstar.widgets'
]);
notif = await mw.notify(
new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, {
message: getMsg('watched', pn)
}).$element,
{ tag: 'listtools' }
);
} catch {
notif = await mw.notify(getMsg(action + 'Fail', pn), {
tag: 'listtools',
type: 'error'
});
} finally {
$wrapper.html($link);
}
};
let extGetMain = function () {
return this.title;
};
let re = new RegExp(`(?:\\?title=|${
mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), ''))
})([^#&?]+)`);
let processed = new WeakSet();
let processLinks = ($links, module, titles) => {
let isBatch = !!titles;
titles = titles || new Set();
$links.each(function (i) {
if (processed.has(this)) return;
let $link = $links.eq(i);
let pn;
if (module.useText) {
pn = $link.text();
} else {
let match = $link.attr('href')?.match(re);
if (!match) return;
pn = decodeURIComponent(match[1]);
}
let t = mw.Title.newFromText(pn);
if (!t) return;
if (module.titlesOnly) {
let text = $link.text();
if (text !== pn.replaceAll('_', ' ') &&
(text !== t.getMainText() || t.namespace === 2)
) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.namespace < 0) return;
if ($link.hasClass('new')) {
t.missing = true;
}
titles.add(t.getSubjectPage().toText());
}
let $tools = $('<span>').addClass('listtools mw-changeslist-links')
.data('listtools', t);
tools.forEach(tool => {
addTool($tools, tool);
});
if ($link.is(':is(del, bdi) > :only-child')) {
if (module.position === 'end') {
$link.parent().parent().append(' ', $tools);
} else {
$link.parent().after(' ', $tools);
}
} else if (module.position === 'end') {
$link.parent().append(' ', $tools);
} else {
$link.after(' ', $tools);
}
if (module.post) {
module.post($tools);
}
processed.add(this);
});
if (!isBatch) {
getWatched(titles);
}
};
let tools = [
{
name: 'edit',
url: t => t.getUrl({ action: 'edit' })
},
{
name: 'hist',
url: t => !t.missing && t.getUrl({ action: 'history' })
},
{
name: 'links',
url: t => mw.util.getUrl('Special:WhatLinksHere/' + t)
},
{
name: 'watch',
url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }),
callback: watchHandler
}
];
let addTool = ($tools, tool, escapedName) => {
let t = $tools.data('listtools');
let $duplicate = escapedName &&
$tools.children('.listtools-' + escapedName);
let url = tool.url;
if (typeof url === 'function') {
url = url(t);
if (!url) {
$duplicate?.remove();
return;
}
}
let $link = $('<a>').attr('href', url).text(getMsg(tool.name));
if (t.host) {
$link.prop('host', t.host);
}
if (tool.callback) {
$link.on('click', tool.callback);
}
let $wrapper = $('<span>').addClass('listtools-' + tool.name)
.append($link);
let $next = tool.next && $tools.children('.listtools-' + tool.next);
if ($next?.length) {
$duplicate?.remove();
$next.before($wrapper);
} else if ($duplicate?.length) {
$duplicate.replaceWith($wrapper);
} else {
$tools.append($wrapper);
}
};
let extend = tool => {
if (tool.label && !Object.hasOwn(messages, tool.label)) {
messages[tool.name] = tool.label;
}
if (tool.next) {
tool.next = $.escapeSelector(tool.next);
}
let existingTool = tools.find(t => t.name === tool.name);
if (existingTool) {
Object.assign(existingTool, tool);
} else {
tools.push(tool);
}
let escapedName = existingTool && $.escapeSelector(tool.name);
let $allTools = $('.listtools');
$allTools.each(function (i) {
addTool($allTools.eq(i), tool, escapedName);
});
};
let getWatched = async titles => {
if (!Array.isArray(titles)) {
titles = [...titles].slice(0, 500);
}
if (!titles.length) return;
(await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages.forEach(page => {
if (!page.watched) return;
$(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`)
.attr('href', mw.util.getUrl(page.title, { action: 'unwatch' }))
.text(getMsg('unwatch'));
});
getWatched(titles.slice(50));
};
mw.hook('listtools.ready').fire(extend);
let catTreeCallback = (records, observer) => {
let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a');
if ($links.length) {
observer.takeRecords();
observer.disconnect();
processLinks($links, catTreeModule);
}
};
let catTreeModule = {
selector: '.CategoryTreeItem > bdi > a',
types: [14, 'CategoryTree'],
position: 'end',
post: $tools => {
$tools.parent().next('.CategoryTreeChildren').each(function () {
new MutationObserver(catTreeCallback)
.observe(this, { childList: true });
});
}
};
let modules = [
{
selector: '#mw-pages li > a, #mw-pages li > span > a',
types: [14]
},
catTreeModule,
{
selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a',
types: [6]
},
{
selector: '#mw-globalusage-result a',
types: ['GlobalUsage']
},
{
selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a',
types: ['Search']
},
{
selector: '.mw-search-createlink a',
types: ['Search'],
titlesOnly: true
},
{
selector: '#watchlist-edit-form .cdx-table td > label > a',
types: ['EditWatchlist']
},
{
selector: '.plainlinks > li > a',
types: ['AbuseLog'],
titlesOnly: true
},
{
selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)',
types: ['Allmessages'],
position: 'end'
},
{
selector: '.mw-spcontent li a',
types: ['DisambiguationPageLinks', 'Listredirects'],
titlesOnly: true
},
{
selector: 'li > a:first-child',
types: ['FileDuplicateSearch']
},
{
selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a',
types: ['LintErrors'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: 'form > ul > li > a',
types: ['Nuke'],
position: 'end',
titlesOnly: true
},
{
selector: '.page-assessments a',
types: ['PageAssessments'],
titlesOnly: true
},
{
selector: '.TablePager_col_pr_page > a',
types: ['Protectedpages'],
position: 'end'
},
{
selector: '#mw-content-text > ul a',
types: ['Protectedtitles'],
position: 'end'
},
{
selector: '.mw-fr-pending-changes-page-title',
types: ['PendingChanges'],
post: $tools => {
$tools.parent().contents().slice(3).remove();
}
},
{
selector: '#mw-content-text > ul a:first-child',
types: ['StablePages'],
position: 'end'
},
{
selector: '.TablePager_col__page a',
types: ['TopicSubscriptions']
},
{
selector: '.undeleteResult > a',
types: ['Undelete'],
position: 'end',
useText: true
},
{
selector: '.TablePager_col_img_name > a:first-child',
// types: ['Listfiles'],
position: 'end'
},
{
selector: '.mw-newpages-pagename',
post: $tools => {
let $contents = $tools.parent().contents();
$contents.slice(
$contents.index($tools) + 1,
$contents.index($contents.filter('.mw-newpages-length'))
).replaceWith(' ');
}
},
{
selector: '#mw-whatlinkshere-list li > bdi > a'
},
{
selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)',
titlesOnly: true
},
{
selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a',
types: ['Log'],
titlesOnly: true
},
{
selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a',
types: ['ComparePages'],
position: 'end'
},
{
selector: '#movepage-oldlink, #movepage-newlink',
types: ['Movepage']
},
{
selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)',
types: ['Undelete'],
useText: true
},
{
selector: '.galleryfilename, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
}());
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// }).then(response => {
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// }).always(() => {
// $wrapper.html($link);
// });
// }
// });
extend({
name: 'copy',
url: '#',
callback: function (e) {
e.preventDefault();
let text = $(this).closest('.listtools').data('listtools').toText();
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch (err) {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
}
});
});
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' &&
(function consecudiff() {
mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}');
let isHist = mw.config.get('wgAction') === 'history';
class Consecudiff {
constructor(lis, isContribs) {
this.isContribs = isContribs;
this.isEnhanced = !isHist && !isContribs &&
lis[0].classList.contains('mw-enhanced-rc');
this.threshold = isContribs ? window.consecudiffContribsThreshold || 120
: isHist ? window.consecudiffHistThreshold || 720
: window.consecudiffThreshold || 720;
this.strictMode = !isContribs &&
!!window.consecudiffDetectInterruptions;
this.diffSelector = isHist
? 'a.mw-history-histlinks-previous'
: '.mw-changeslist-diff';
this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' ||
(isHist || isContribs) && 'a.mw-changeslist-date';
this.hybridSelector = this.diffSelector;
if (this.permaSelector) {
this.hybridSelector += ', ' + this.permaSelector;
}
this.topClass = isContribs
? 'mw-contributions-current'
: 'mw-changeslist-last';
let dependencies = ['mediawiki.util'];
if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') {
dependencies.push('mediawiki.language.months');
}
mw.loader.using(dependencies, () => {
let chunks;
if (isHist) {
chunks = this.chunkByUser(lis);
} else {
chunks = [];
this.groupByTitle(lis).forEach(group => {
chunks.push(...this.chunkByUser(group));
});
}
let subchunks = [];
chunks.forEach(chunk => {
subchunks.push(...this.divideByDate(chunk));
});
let linkPairs = [];
subchunks.forEach(subchunk => {
linkPairs.push(...this.makeLinks(subchunk));
});
linkPairs.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
});
}
groupByTitle(lis) {
let selector = this.isContribs
? '.mw-contributions-title'
: '.mw-changeslist-title';
let lisByTitle = {};
lis.forEach(li => {
let link = (this.isEnhanced ? li.closest('table') : li)
.querySelector(selector);
if (!link) return;
let title = link.textContent;
if (!lisByTitle.hasOwnProperty(title)) {
lisByTitle[title] = [];
}
lisByTitle[title].push(li);
});
return Object.values(lisByTitle).filter(group => group.length > 1);
}
chunkByUser(lis) {
if (this.isSingleContribs) {
return [lis];
}
let chunks = [], lastSplitAt = 0, prevUser;
this.isSingleContribs = lis.some((li, i) => {
let link = li.querySelector('.mw-userlink');
if (!link && this.isContribs) {
return true;
}
let user = link && link.textContent;
if (!link || i && user !== prevUser) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevUser = user;
});
if (this.isSingleContribs) {
return [lis];
}
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
divideByDate(lis) {
let chunks = [], lastSplitAt = 0, prevDate;
lis.forEach((li, i) => {
let date;
if (isHist || this.isContribs) {
date = this.parseDate(
li.querySelector('.mw-changeslist-date').textContent
);
} else {
date = Date.parse(
li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z')
);
}
if (date) {
date = date / 60000;
}
if (i && prevDate - date > this.threshold) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
prevDate = date;
if (!this.strictMode || lastSplitAt === i) return;
let prevDiff = lis[i - 1].querySelector(this.diffSelector);
if (prevDiff) {
let prevNext = mw.util.getParamValue('oldid', prevDiff.search);
if (prevNext !== li.dataset.mwRevid) {
chunks.push(lis.slice(lastSplitAt, i));
lastSplitAt = i;
}
}
});
chunks.push(lis.slice(lastSplitAt));
return chunks.filter(chunk => chunk.length > 1);
}
makeLinks(lis) {
let count = lis.length;
let firstPerma;
let start = lis.findIndex(li => (
firstPerma = li.querySelector(this.hybridSelector)
));
if (start === -1 || count - start < 2) return [];
let end, lastDiff;
for (let i = count - 1; i > start; i--) {
if (!isHist && !this.isContribs) {
lastDiff = lis[i].querySelector(this.diffSelector);
if (lastDiff ||
lis[i].classList.contains('mw-changeslist-src-mw-new')
) {
end = i + 1;
break;
}
}
if (this.permaSelector && lis[i].querySelector(this.permaSelector)) {
end = i + 1;
break;
}
}
if (!end) return [];
count = end - start;
let params = { diff: lis[start].dataset.mwRevid };
if (lastDiff) {
params.oldid = mw.util.getParamValue('oldid', lastDiff.search);
} else {
params.oldid = lis[end - 1].dataset.mwRevid;
if (isHist && lis[end - 1].querySelector(this.diffSelector) ||
this.isContribs && !lis[end - 1].querySelector('.newpage')
) {
params.direction = 'prev';
}
}
let title = !isHist && mw.util.getParamValue('title', firstPerma.search);
let url = mw.util.getUrl(title, params);
let classes = 'consecudiff';
if (!isHist && lis[start].classList.contains(this.topClass)) {
classes += ' consecudiff-top';
}
return lis.slice(start, end).map((li, i) => [
$('<span>').addClass(classes).append(
$('<a>')
.attr('href', url)
.text(this.convertNumber(count - i + '/' + count))
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
parseDate(s) {
let date = Date.parse(s);
if (date) {
return date;
}
if (s.includes(',')) date = Date.parse(s.replace(',', ''));
if (date) {
return date;
}
if (mw.loader.getState('mediawiki.language.months') !== 'ready') return;
s = s.replace(/\D/g, c => {
let n = mw.language.convertNumber(c, true);
return Number.isNaN(n) ? c : n;
});
let h, m;
s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => {
h = $1;
m = $2;
return ' ';
});
if (!h) return;
let y, dateFirst;
s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => {
y = $2;
dateFirst = /\d/.test($1);
return $1 + ' ';
});
if (!y) return;
let mo, d;
if (dateFirst) {
[d, s] = this.getDate(s);
if (!d) return;
[mo, s] = this.getMonth(s);
if (mo === -1) return;
} else {
[mo, s] = this.getMonth(s);
if (mo === -1) return;
[d, s] = this.getDate(s);
if (!d) return;
}
return new Date(y, mo, d, h, m).getTime();
}
getMonth(s) {
if (!this.months) {
this.months = mw.language.months.abbrev
.concat(mw.language.months.names, mw.language.months.genitive)
.reverse();
}
let mo = this.months.findIndex(mn => {
let temp = s.replace(mn, ' ');
if (temp !== s) {
s = temp;
return true;
}
});
if (mo === -1) {
let [numeric, temp] = this.getDate(s);
numeric = parseInt(numeric);
if (numeric > 0 && numeric < 13) {
mo = numeric - 1;
s = temp;
}
} else {
mo = 11 - mo % 12;
}
return [mo, s];
}
getDate(s) {
let d;
s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => {
d = $2;
return $1 + ' ';
});
return [d, s];
}
convertNumber(num) {
try {
return mw.language.convertNumber(num);
} catch (e) {
return num;
}
}
}
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-body').each(function () {
let lis = this.querySelectorAll('.mw-contributions-list > li');
if (lis.length > 1) {
new Consecudiff([...lis], !isHist);
}
});
if (isHist) return;
let $lists = $content.filter('.mw-changeslist');
if (!$lists.length) {
$lists = $content.find('.mw-changeslist');
}
$lists.each(function () {
let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]');
if (lis.length > 1) {
new Consecudiff([...lis]);
}
});
});
}());
if (mw.config.get('wgNamespaceNumber') === 14 && (
mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId')
)) {
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter',
'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement',
'mediawiki.interface.helpers.styles', 'user.options'
]);
}
$(function moveHistory() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.moveHistoryDialog) {
window.moveHistoryDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter',
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions',
'mediawiki.interface.helpers.styles'
]);
});
});
});
$(function sectionSearch() {
if (!document.getElementById('p-tb')) return;
mw.loader.using('mediawiki.util', () => {
let clicked;
mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.sectionSearchDialog) {
window.sectionSearchDialog.open();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows',
'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget'
]);
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' &&
mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() {
mw.hook('wikipage.content').add($content => {
let $table = $content.find('.mw-centralauth-wikislist').has('td');
if (!$table.length) return;
$table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
[10, 828].includes(mw.config.get('wgNamespaceNumber')) &&
!mw.config.get('wgTitle').endsWith('/doc') &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's');
// ['edit', 'submit'].includes(mw.config.get('wgAction')) &&
// $(function templatePreviewGuard() {
// let button = document.querySelector('input[name="wpTemplateSandboxPreview"]');
// if (!button) return;
// let proceed;
// button.addEventListener('click', e => {
// if (proceed) {
// proceed = false;
// return;
// }
// e.preventDefault();
// e.stopPropagation();
// let formData = new FormData(button.form);
// let page = formData.get('wpTemplateSandboxPage');
// let temp = formData.get('wpTemplateSandboxTemplate');
// if (!page || !temp) return;
// mw.loader.using('mediawiki.api').then(() => (
// new mw.Api().get({
// action: 'query',
// titles: page,
// prop: 'templates',
// tltemplates: temp,
// formatversion: 2
// })
// )).always(response => {
// if (((((response || {}).query || {}).pages || [])[0] || {}).templates ||
// confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`)
// ) {
// proceed = true;
// button.click();
// }
// });
// }, true);
// if (!mw.config.get('wgArticleId')) return;
// let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget');
// if (!widgetEl) return;
// let pn = mw.config.get('wgPageName').replace(/_/g, ' ');
// mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => (
// new mw.Api().get({
// action: 'query',
// titles: pn,
// prop: 'transcludedin',
// tiprop: 'title',
// tilimit: 'max',
// formatversion: 2
// })
// )).then(response => {
// if (!response.batchcomplete) return;
// let pages = response.query.pages[0].transcludedin
// .filter(o => o.title !== pn);
// if (!pages.length) return;
// let widget = OO.ui.infuse(widgetEl);
// if (pages.length === 1) {
// widget.setValue(pages[0].title);
// return;
// }
// widget.$element.replaceWith(
// new OO.ui.ComboBoxInputWidget({
// id: 'wpTemplateSandboxPage',
// maxlength: widget.$input.prop('maxLength'),
// name: widget.$input.prop('name'),
// options: pages
// .sort((a, b) => a.ns - b.ns || -(a.title < b.title))
// .map(o => ({ data: o.title })),
// placeholder: widget.$input.prop('placeholder'),
// tabIndex: widget.getTabIndex(),
// value: widget.getValue()
// }).on('enter', e => {
// e.preventDefault();
// button.click();
// }).$element
// );
// });
// });
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(function autoSectionLink() {
let form = document.getElementById('editform');
if (!form) return;
let formData = new FormData(form);
let section = formData.get('wpSection');
if (section === 'new') return;
let widget = document.getElementById('wpSummaryWidget');
if (!widget) return;
let isOld = formData.get('altBaseRevId') > 0 ||
(formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId');
mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'oojs-ui-core',
'oojs-ui.styles.icons-editing-core'
], () => {
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
let button = new OO.ui.ButtonWidget({
framed: false,
icon: 'undo',
classes: ['autosectionlink-button'],
invisibleLabel: true,
label: 'Restore previous section link'
}).toggle().on('click', () => {
let cache = button.getData();
input.setValue(input.getValue().replace(
/^(\/\*.*?\*\/)?\s*/,
cache[0] ? '/* ' + cache[0] + ' */ ' : ''
));
updatePreview(cache[0]);
cache.reverse();
}).on('toggle', () => {
input.$input.css('width', `calc(100% - ${button.$element.width()}px)`);
});
input.$input.after(button.$element);
let update = mw.util.debounce($diff => {
let lines = $textarea.textSelection('getContents').replace(/\s+$/, '').split('\n');
let firstLineNum;
if (isOld) {
let i, lastLineNum;
$diff.find('td:last-child').each(function () {
if (this.classList.contains('diff-lineno')) {
i = this.textContent.replace(/\D+/g, '') - 1;
} else if (this.classList.contains('diff-context')) {
i++;
} else if (this.classList.contains('diff-addedline')) {
i++;
if (!firstLineNum) {
firstLineNum = i;
}
lastLineNum = i;
} else if (this.classList.contains('diff-empty')) {
if (!firstLineNum) {
firstLineNum = i === 0 ? 1 : i;
}
lastLineNum = i;
}
});
lines.length = lastLineNum || 0;
} else {
let origLines = $textarea.prop('defaultValue').replace(/\s+$/, '').split('\n');
firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1;
if (!firstLineNum) {
firstLineNum = lines.length < origLines.length
? lines.length
: 1;
}
for (let i = 1, x = lines.length, y = origLines.length;
(section ? i < x : i <= x) && lines[x - i] === origLines[y - i];
i++
) {
lines.pop();
}
}
let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/, lowest = 7;
lines.slice(firstLineNum).forEach(line => {
let match = line.match(re);
if (match && match[1].length < lowest) {
lowest = match[1].length;
}
});
let head;
lines.slice(0, firstLineNum).reverse().some(line => {
let match = line.match(re);
if (match && match[1].length < lowest) {
head = match[2];
return true;
}
});
head = head ? head
.replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3')
.replace(/''(.+?)''/g, '$1')
.trim() : '';
let match = input.getValue().match(/^(?:\/\*\s*(.*?)\s*\*\/)?\s*(.*?)$/);
let prev = match[1] || '';
if (prev === head) return;
if (section < 1 && lowest === 7 && !head && prev === 'top') return;
input.setValue((head ? '/* ' + head + ' */ ' : '') + match[2]);
button.setData([prev, head]).toggle(true);
updatePreview(head);
}, 500);
let updatePreview = head => {
let $preview = $('.mw-summary-preview > .comment > span[dir="auto"]');
if (!$preview.length) return;
let url = head && mw.util.getUrl() + '#' + head.replace(/ /g, '_');
let text = head && (document.dir === 'rtl' ? '←\u200F' : '→\u200E') + head;
let $ac = $preview.children('.autocomment:first-child');
if ($ac.length && !$ac[0].previousSibling) {
if (head) {
$ac.children('a').attr('href', url).text(text);
} else {
let node = $ac[0].nextSibling;
if (node && node.nodeType === 3) {
node.textContent = node.textContent.replace(/^\s+/, '');
}
$ac.remove();
}
} else if (head) {
$('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replace(/_/g, ' ')
}).text(text),
mw.messages.get('colon-separator', ': ')
).prependTo($preview);
}
};
if (isOld) {
mw.hook('wikipage.diff').add(update);
} else {
$textarea.on('input', update);
mw.hook('ext.CodeMirror.switch').add((on, $codeMirror) => {
if (on && $codeMirror[0].CodeMirror) {
$codeMirror[0].CodeMirror.on('change', update);
}
});
mw.hook('ext.CodeMirror.input').add(update);
update();
}
});
mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}');
});
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(function copyRevId() {
let handler = function (e) {
e.preventDefault();
let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid ||
this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!text) return;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
document.execCommand('copy');
$input.remove();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('id')
)
);
});
}());
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
let handler = async function (e) {
e.preventDefault();
let td = this.closest('.diff td');
let rev = td
? td.querySelector('[data-mw-revid]')?.dataset.mwRevid
: this.closest('[data-mw-revid]')?.dataset.mwRevid;
if (!rev) {
mw.notify(`Couldn't get the revision.`, {
tag: 'markasunseen',
type: 'error'
});
return;
}
let pn = td
? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title')
: mw.config.get('wgPageName');
if (!pn) return;
await mw.loader.using('mediawiki.api');
let result = (await new mw.Api().postWithEditToken({
action: 'setnotificationtimestamp',
[td ? 'newerthanrevid' : 'torevid']: rev,
titles: pn,
formatversion: 2
})).setnotificationtimestamp?.[0];
if (Object.hasOwn(result, 'notificationtimestamp')) {
mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'success'
});
} else if (result?.notwatched) {
mw.notify('This page is not on your watchlist.', {
tag: 'markasunseen',
type: 'warn'
});
} else {
mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, {
tag: 'markasunseen',
type: 'error'
});
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1').append(
' (',
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions after this one as unseen'
}).on('click', handler).text('unseen'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button',
title: 'Mark revisions since this one as unseen'
}).on('click', handler).text('unseen')
)
);
});
})();
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) ||
(mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) &&
mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() {
let handler = function (e) {
e.preventDefault();
let parent = this.closest('li, td');
let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0];
if (!ts) return;
let user = parent.querySelector('.mw-userlink').textContent;
if (mw.util.isIPv6Address(user)) {
user = user.toUpperCase();
}
let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned';
let text = `{{subst:${temp}|${user}|${ts}}}`;
let $input = $('<input>').attr({
type: 'text',
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$input[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$input.remove();
if (copied) {
mw.notify(`Copied "${text}"`);
} else {
mw.notify('Copy failed', { type: 'error' });
}
};
mw.hook('wikipage.diff').add($diff => {
$diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () {
if (mw.config.get('wgWikiID') === 'metawiki') {
return true;
}
let link = this.querySelector('strong > a') ||
this.parentElement.querySelector('#differences-prevlink, #differences-nextlink');
if (!link) return;
let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search));
return t.isTalkPage() || t.namespace === 4;
}).append(
' (',
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig'),
')'
);
});
if (mw.config.get('wgAction') !== 'history') return;
mw.hook('wikipage.content').add($content => {
$content.find('.mw-pager-tools').append(
$('<span>').append(
$('<a>').attr({
href: '#',
role: 'button'
}).on('click', handler).text('sig')
)
);
});
});
// mw.config.get('wgAction') === 'history' &&
// mw.loader.using('mediawiki.util', function () {
// mw.hook('wikipage.content').add($content => {
// $content.find('a.mw-changeslist-date').after(function () {
// return [
// ' (',
// $('<a>').attr('href', mw.util.getUrl(null, {
// action: 'edit',
// oldid: this.closest('li').dataset.mwRevid
// })).text('e'),
// ')'
// ];
// });
// });
// });
// ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
// mw.hook('wikipage.content').add($content => {
// $content.find('.mw-changeslist-history').parent().after(function () {
// return $('<span>').append(
// $('<a>').attr(
// 'href',
// this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit'
// ).text('e')
// );
// });
// });
if (screen.width < 500) {
mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}');
location.hash && $(() => {
let target = document.querySelector(':target');
if (target?.getBoundingClientRect().top < 0) {
target.scrollIntoView();
}
});
}
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(mw.config.exists('wgCodeEditorCurrentLanguage') ||
mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') &&
(function saveNEdit() {
let notif;
$(document.body).on('click', '#wpSave', async function (e) {
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey ||
e.originalEvent?.defaultPrevented
) {
return;
}
e.preventDefault();
await mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core'
]);
let button = OO.ui.infuse(this.parentElement).setDisabled(true);
let $textarea = $('#wpTextbox1');
let text = $textarea.textSelection('getContents');
let $summary = $('#wpSummary');
let formData = new FormData(this.form);
let promise = new mw.Api().postWithEditToken({
action: 'edit',
title: mw.config.get('wgPageName'),
text: text,
section: formData.get('wpSection') || undefined,
summary: $summary.textSelection('getContents'),
[$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1,
baserevid: formData.get('editRevId'),
basetimestamp: formData.get('wpEdittime'),
starttimestamp: formData.get('wpStarttime'),
watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch',
watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined,
undo: formData.get('wpUndidRevision') || undefined,
undoafter: formData.get('wpUndoAfter') || undefined,
contentformat: formData.get('format'),
contentmodel: formData.get('model'),
assertuser: mw.config.get('wgUserName'),
formatversion: 2
});
notif?.close();
notif = null;
try {
let response = await promise;
if (response?.edit?.result !== 'Success') throw '';
$('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove();
$textarea.data('origtext', text).prop('defaultValue', text);
$summary.val($summary.prop('defaultValue'));
if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') {
let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js'];
storage.deleteData(mw.config.get('wgPageName'));
storage.closeDatabase();
}
notif = await mw.notify(response.edit.nochange ? 'No change' : [
document.createTextNode('Saved'),
$('<p>').append(
new OO.ui.ButtonWidget({
href: mw.util.getUrl(),
target: '_blank',
label: 'View'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, {
diff: response.edit.newrevid || 'cur',
diffonly: 1
}),
target: '_blank',
label: 'Diff'
}).$element,
new OO.ui.ButtonWidget({
href: mw.util.getUrl(null, { action: 'history' }),
target: '_blank',
label: 'History'
}).$element
)[0]
], { tag: 'savenedit' });
} catch (error) {
notif = await mw.notify(error?.error?.info || error || 'Save failed', {
autoHideSeconds: 'long',
tag: 'savenedit',
type: 'error'
});
} finally {
button.setDisabled();
}
});
}());
mw.config.get('wgNamespaceNumber') === 0 &&
mw.config.get('wgAction') === 'view' &&
mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) &&
$(() => {
let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, ''));
let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`);
if (!$links.length) return;
let titles = {};
$links = $links.filter(function () {
let text = this.textContent;
return !(titles[text] = Object.hasOwn(titles, text));
});
mw.notify(
$links.length === 1
? $links.clone()
: $('<ul>').append($links.clone().wrap('<li>').parent()),
{ autoHideSeconds: 'long' }
);
});
['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
$.when($.ready, mw.loader.using([
'user.options', 'mediawiki.util', 'mediawiki.api'
])).then(function rcMuter() {
let os = mw.user.options.get('userjs-rcmuter');
let set = new Set(os && os.split('|'));
let save = () => {
let ns = [...set].join('|');
if (ns === mw.user.options.get('userjs-rcmuter')) return;
new mw.Api().saveOption('userjs-rcmuter', ns);
mw.user.options.set('userjs-rcmuter', ns);
$edit.attr('data-rcmuter', set.size);
};
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
'data-rcmuter': set.size
}).text('Edit muted').on('click', e => {
e.preventDefault();
mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]).then(() => OO.ui.getWindowManager().getWindow('message')).then(dialog => {
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...set]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
let instance = dialog.open({
message: $([
document.createTextNode('Muted users:'),
multiselect.$element[0]
]),
size: 'medium'
});
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().menu.toggle(false);
});
});
instance.closed.then(result => {
if (!result || result.action !== 'accept') return;
set = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
});
let buttonsShown;
let $toggleButtons = $('<a>').attr('href', '#').text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (buttonsShown) {
mw.hook('wikipage.content').remove(addButtons);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addButtons);
this.textContent = 'Hide toggle buttons';
}
buttonsShown = !buttonsShown;
});
let $toggle = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleSpan = $('<span>').hide().append($toggle);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleButtons),
$toggleSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-userlink ~ .mw-usertoollinks, .mw-changeslist-line-inner-userLink ~ .mw-changeslist-line-inner-userTalkLink')
.prevAll('.mw-userlink, .mw-changeslist-line-inner-userLink')
.last().text().trim();
if (!user) {
mw.notify(`Can't retrieve the username.`, {
tag: 'rcmuter',
type: 'error'
});
return;
}
let muting = this.parentElement.classList.toggle('rcmuter-unmute');
set[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addButtons = $content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) {
$content = $('#mw-content-text');
if ($content.has('.rcmuter-toggle').length) return;
}
let $tools = $content.find('.mw-usertoollinks.mw-changeslist-links');
let $muted = $tools.filter('.rcmuter-muted *');
$tools.not($muted).append(
$('<span>').addClass('rcmuter-toggle').append(
$('<a>').attr('href', '#').text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr('href', '#').text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let muted = set.has(this.textContent);
if (muted) mutedCount++;
return muted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!set.size) {
$toggleSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-userlink:only-child')
.filter(filter).closest('table').addClass('rcmuter-muted');
$content.find('.mw-userlink:not(.changedby > *, .comment *, .rcmuter-muted *)')
.filter(filter).closest('.mw-changeslist-line, table').addClass('rcmuter-muted')
.closest('table.mw-enhanced-rc').find('.changedby > .mw-userlink').filter(filter).addClass('rcmuter-muted');
$toggleSpan.toggle(!!mutedCount);
$toggle.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() {
if (!document.getElementById('p-tb')) return;
let messages = Object.assign({
portlet: 'RefRenamer',
loading: 'Loading RefRenamer...'
}, window.refrenamerMessages);
let clicked;
mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => {
e.preventDefault();
if (clicked) {
if (window.refRenamer) {
window.refRenamer();
}
return;
}
clicked = true;
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
if (['edit', 'submit'].includes(mw.config.get('wgAction'))) {
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's');
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's');
}
mw.config.get('wgAction') !== 'history' &&
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's');
mw.config.exists('wgDiffNewId') &&
mw.config.get('wgDiscussionToolsFeaturesEnabled') &&
(function () {
let data = {}, clickHandler, autoClear, run;
window.dtc = data;
let highlight = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js']
.highlightNewComments(mw.dt.pageThreads, true, ids);
if (clickHandler) {
$(document.body).off('click', clickHandler);
return;
}
$._data(document.body, 'events').click.some(o => {
if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) {
$(document.body).off('click', o.handler);
clickHandler = o.handler;
return true;
}
});
$._data(window, 'events').popstate.some(o => {
if (String(o.handler).includes('highlighter.highlightTargetComment(')) {
$(window).off('popstate', o.handler);
return true;
}
});
};
let scroll = revId => {
let ids = data[revId];
if (!ids || !ids.length) return;
let yToSpan = Object.fromEntries(
ids.map(id => document.getElementById(id)).filter(Boolean)
.map(span => [span.getBoundingClientRect().y, span])
);
let ys = Object.keys(yToSpan);
if (!ys.length) return;
let lower = ys.filter(y => y > 10);
if (!lower.length ||
Math.max(...lower) < document.documentElement.clientHeight
) {
yToSpan[Math.min(...ys)].scrollIntoView();
} else {
yToSpan[Math.min(...lower)].scrollIntoView();
}
};
let scrollToNext = function (e) {
e.preventDefault();
let revId = mw.config.get('wgDiffOldId');
if (!revId || !data[revId]) return;
let i = data[revId].indexOf(
this.closest('[data-mw-thread-id]').dataset.mwThreadId
);
if (i === -1) return;
let next = data[revId][i + 1] || data[revId][0];
document.getElementById(next).scrollIntoView();
};
mw.hook('wikipage.content').add(async $content => {
let revId = mw.config.get('wgDiffOldId');
if (!revId) return;
let param = new URLSearchParams(location.search).get('diffonly');
if (param && param !== '0') return;
if (data[revId]) {
highlight(revId);
return;
}
await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']);
let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp'));
data[revId] = mw.dt.pageThreads.getCommentItems()
.filter(c => c.timestamp > begin).map(c => c.id);
if (!data[revId].length) return;
await new Promise(setTimeout);
highlight(revId);
$content.find('.ext-discussiontools-init-replylink-buttons').filter(function () {
return data[revId].includes(this.dataset.mwThreadId);
}).children('span:last-of-type').before(
' | ',
$('<a>').attr({
href: '#',
role: 'button'
}).text('next').on('click', scrollToNext)
);
if (run || !document.getElementById('p-tb')) return;
run = true;
let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext');
portlet.firstElementChild.addEventListener('click', e => {
e.preventDefault();
scroll(mw.config.get('wgDiffOldId'));
});
mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => {
e.preventDefault();
autoClear = !autoClear;
if (autoClear) {
$(document.body).on('click', clickHandler)[0].click();
} else {
highlight(mw.config.get('wgDiffOldId'));
}
});
mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`);
});
}());
mw.config.get('wgNamespaceNumber') === 6 &&
mw.config.get('wgAction') === 'view' &&
mw.hook('wikipage.content').add($content => {
$content.find('.filehistory .mw-usertoollinks-contribs').after(function () {
return [
' | ',
$('<a>').attr('href', `${
mw.config.get('wgScript')
}?title=Special:ListFiles/${
this.pathname.replace(/^.+\//, '')
}&ilshowall=1`).text('uploads')
];
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(function () {
if (!$('input[name="wpSection"]').val()) return;
mw.hook('wikipage.content').add(async $content => {
let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text');
if (!$refs.length) return;
let ids = {};
$refs.each(function () {
ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this;
});
let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`);
$($.parseHTML(response)).find('.mw-reference-text').each(function () {
ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this);
});
});
});
mw.hook('moremenu.ready').add(config => {
$('#mm-page-purge-cache > a').on('click', e => {
e.preventDefault();
new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
}).then(() => {
location.href = mw.util.getUrl();
});
});
$('#mm-page-search-search-history-wikiblame > a').on('click', function (e) {
e.preventDefault();
let q = prompt();
if (q === null) return;
let href = this.href;
if (q) {
let removal = q[0] === '!';
if (removal) {
q = q.slice(1);
}
href += '&needle=' + encodeURIComponent(q);
if (removal) {
href += '&binary_search_inverse=on';
}
href += '&force_wikitags=on';
}
open(href, '_blank');
});
$('#mm-page-expand-templates > a').on('click auxclick', function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val());
let url = revId
? '/w/rest.php/v1/revision/' + revId
: '/w/rest.php/v1/page/' + config.page.encodedName;
$.get(url).then(response => {
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', response.source],
['wpContextTitle', config.page.name],
['wpRemoveComments', 1]
].map(([n, v]) => $('<input>').attr({
name: n,
type: 'hidden'
}).val(v))
).appendTo(document.body).trigger('submit').remove();
});
});
});
mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' &&
mw.hook('apisandbox.formatRequest').add((...args) => {
args[4].complete = function () {
setTimeout(() => {
mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active'));
}, 100);
};
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
let section = $('input[name="wpSection"]').val();
if (section) {
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let save = () => {
let newSource = $textarea.textSelection('getContents');
if (newSource === source) {
mw.storage.session.remove('editfullpage');
} else {
mw.storage.session.setObject('editfullpage', [
mw.config.get('wgPageName'),
section,
newSource.trimEnd(),
infuseAndCall('#wpSummaryWidget', 'getValue') || '',
Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0,
Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0,
infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite'
]);
}
};
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
setInterval(() => {
mw.requestIdleCallback(save);
}, 3000);
window.addEventListener('beforeunload', save);
return;
}
let data = mw.storage.session.getObject('editfullpage');
mw.storage.session.remove('editfullpage');
console.log(data);
if (!data || data[0] !== mw.config.get('wgPageName')) return;
let isNew = data[1] === 'new';
let isLead = data[1] === '0';
let $textarea = $('#wpTextbox1');
let source = $textarea.prop('defaultValue');
let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' };
let orig = [];
if (isNew) {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']);
newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n';
start = source.length;
} else {
await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']);
let { parse } = await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'sections',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1,
formatversion: 2
});
let target = !isLead && parse.sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = parse.sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n');
start = isLead ? 0 : target.byteoffset;
} else {
newSource = source + '\n\n' + data[2] + '\n';
start = source.length;
msg = `Section restored. Couldn't find the section. The source is appended at bottom.`;
notifOpts.type = 'warn';
}
orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue');
infuseAndCall('#wpSummaryWidget', 'setValue', data[3]);
}
$textarea.textSelection('setContents', newSource);
orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected');
infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]);
orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected');
infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]);
orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue');
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]);
setTimeout(() => {
$textarea.textSelection('setSelection', { start });
});
let notif = await mw.notify($([
document.createTextNode(msg || 'Section restored.'),
$('<p>').append(
new OO.ui.ButtonWidget({
flags: 'destructive',
label: 'Discard'
}).on('click', () => {
$textarea.textSelection('setContents', source);
if (orig[0]) {
infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]);
}
infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]);
infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]);
infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]);
notif.close();
}).$element
)[0]
]), notifOpts);
});
mw.config.exists('wgPostEdit') &&
mw.loader.using('mediawiki.storage', () => {
mw.storage.session.remove('editfullpage');
});
mw.config.get('wgAction') === 'history' &&
mw.hook('wikipage.content').add(async $content => {
if (!$content.has('.mw-history-line-updated').length) return;
let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href');
if (!href) {
await mw.loader.using(['mediawiki.api', 'mediawiki.util']);
let page = (await new mw.Api().get({
action: 'query',
titles: mw.config.get('wgPageName'),
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev >= page.lastrevid) return;
href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
}
$content.find('.mw-history-compareselectedversions-button').first().after(
' ',
$('<a>').attr({
class: 'unseendiff',
href: href
}).text('unseen')
);
});
(async () => {
let cspn = mw.config.get('wgCanonicalSpecialPageName');
let isBp = cspn === 'Blankpage';
if (!isBp && cspn !== 'Watchlist') return;
await mw.loader.using('mediawiki.util');
let notify = async (text, options, pn) => {
let msg = [document.createTextNode(text)];
if (pn) {
msg.push(
$('<p>').append(
$('<a>').attr('href', mw.util.getUrl(pn)).text(pn),
' ',
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'edit' }))
.text('edit')
),
$('<span>').append(
$('<a>')
.attr('href', mw.util.getUrl(pn, { action: 'history' }))
.text('history')
)
)
)[0]
);
}
if (isBp) {
await $.ready;
$('#mw-content-text').html(msg);
} else {
return mw.notify(msg, Object.assign(options || {}, {
tag: 'unseendiff'
}));
}
};
let getUrl = async pn => {
await mw.loader.using('mediawiki.api');
let page = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'info',
inprop: 'notificationtimestamp',
formatversion: 2
})).query.pages[0];
if (!page.notificationtimestamp) {
notify(`Couldn't get the last seen time.`, {
type: 'warn'
}, pn);
return;
}
let rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (rev === page.lastrevid) {
notify('Already seen.', {
type: 'warn'
}, pn);
return;
}
if (!rev) {
rev = (await new mw.Api().get({
action: 'query',
titles: pn,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
rvdir: 'newer',
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev) {
notify(`Couldn't get the last seen revision.`, {
type: 'warn'
}, pn);
return;
}
}
if (rev > page.lastrevid) {
notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, {
autoHideSeconds: 'long',
type: 'warn'
}, pn);
return;
}
return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev });
};
if (isBp) {
let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1];
if (!pn) return;
notify('Loading...', null, pn);
let href = await getUrl(pn);
if (!href) return;
notify('Redirecting...', null, pn);
location.href = href;
return;
}
let handler = async function (e) {
if (e.which > 2) return;
e.preventDefault();
let pn = this.dataset.pn;
if (!pn) {
notify(`Couldn't get the page name.`, {
type: 'error'
});
return;
}
let notifPromise = notify('Loading...', {
autoHideSeconds: 'long'
});
let href = await getUrl(pn);
if (!href) return;
$(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({
class: 'unseendiff',
href: href,
target: '_blank'
}).off('click auxclick', handler);
if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) {
open(href);
} else {
this.click();
}
(await notifPromise).close();
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner'
).each(function () {
let pn = this.dataset.targetPage ||
this.closest('[data-target-page]')?.dataset.targetPage ||
this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage;
if (!pn) return;
$('<span>').append(
$('<a>').attr({
class: 'unseendiff-loader',
href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`),
'data-pn': pn
}).on('click auxclick', handler).text('unseen')
).appendTo(
[...this.querySelectorAll('.mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
});
})();
['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) &&
mw.loader.using('mediawiki.util', () => {
let watched = new Set();
let query = async lis => {
let titles = Object.keys(lis).slice(0, 50);
if (!titles.length) return;
await mw.loader.using('mediawiki.api');
let pages = (await new mw.Api().post({
action: 'query',
titles: titles,
prop: 'info',
inprop: 'notificationtimestamp|watched',
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).query.pages;
for (let page of pages) {
if (!Object.hasOwn(lis, page.title)) continue;
if (page.watched) {
watched.add(page);
$(lis[page.title]).addClass('watched');
}
if (!page.notificationtimestamp) continue;
let rev = (await new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'ids',
rvlimit: 1,
rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1,
formatversion: 2
})).query.pages[0].revisions?.[0].revid;
if (!rev || rev === page.lastrevid) continue;
if (rev > page.lastrevid) {
mw.notify($([
document.createTextNode('Invalid rev for "'),
$('<a>').attr({
href: mw.util.getUrl(page.title, { action: 'history' }),
target: '_blank'
}).text(page.title)[0],
document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`),
]), {
autoHideSeconds: 'long',
type: 'warn'
});
continue;
}
$('<span>').append(
$('<a>').attr({
class: 'unseendiff',
href: mw.util.getUrl(page.title, {
diff: page.lastrevid,
oldid: rev
})
}).text('unseen')
).appendTo(
lis[page.title].map(li => (
[...li.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ')
))
);
}
titles.forEach(title => {
delete lis[title];
});
query(lis);
};
mw.hook('wikipage.content').add($content => {
$content.find(
'.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]'
).each(function () {
let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history');
let pn = link ? new URLSearchParams(link.search).get('title') : '';
$('<span>').append(
$('<a>').attr({
class: 'mw-changeslist-diff',
href: mw.util.getUrl(pn, {
diff: 'cur',
oldid: this.dataset.mwRevid
})
}).text('cur')
).appendTo(
[...this.querySelectorAll(':scope > .mw-pager-tools')].pop() ||
$('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ')
);
});
if (mw.config.get('wgWikiID') === 'wikidatawiki') return;
let lis = {};
$content.find('.mw-contributions-title').each(function () {
let title = this.textContent;
if (!Object.hasOwn(lis, title)) {
lis[title] = [];
}
lis[title].push(this.closest('li'));
});
Object.keys(lis).forEach(title => {
if (watched.has(title)) {
$(lis[title]).addClass('watched');
delete lis[title];
}
});
query(lis);
});
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript');
mw.config.get('wgWikiID') === 'metawiki' &&
(async () => {
let css = mw.loader.addStyleTag(`.wishtitle {
font-size: 90%;
font-style: italic;
word-break: break-word;
}
.wishtitle > a {
color: var(--color-warning, #886425);
}
.wishtitle > a:visited {
color: var(--border-color-warning--hover, #735421);
}
.wishtitle-declined > a {
text-decoration: line-through;
}
.wishtitle-declined > a:hover,
.wishtitle-declined > a:focus,
.mw-underline-always .wishtitle-declined > a {
text-decoration: line-through underline;
}
#watchlist-edit-form .wishtitle {
display: inline-block;
}
.mw-search-result-heading > .wishtitle,
.catchangesviewer-table .wishtitle {
display: block;
}
.catchangesviewer-table:has(.wishtitle) {
white-space: wrap;
}`);
let lang = mw.config.get('wgUserLanguage');
let titles;
let loadTitles = async () => {
await mw.loader.using('mediawiki.storage');
titles = titles || mw.storage.getObject('wishtitles');
if (titles?.lang !== lang) {
titles = { lang, w: [], fa: [] };
}
};
let updateTitles = async (crwstatuses, crwcontinue) => {
await mw.loader.using('mediawiki.api');
let params = {
action: 'query',
list: 'communityrequests-wishes',
crwlang: lang,
crwstatuses: crwstatuses,
crwprop: 'title|updated',
crwsort: 'updated',
crwdir: 'ascending',
crwlimit: 'max',
crwcontinue: crwcontinue,
formatversion: 2
};
if (!crwcontinue && !crwstatuses && titles._) {
params.crwcontinue = `|${titles._}|0`;
}
let response = await new mw.Api().get(params);
let wishes = response?.query?.['communityrequests-wishes'];
if (wishes?.length) {
let $span = $('<span>');
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = $span.html(w.title).text();
if (crwstatuses === 'declined') {
(titles.wd = titles.wd || []).push(id - 1);
}
let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1];
if (!faId) return;
titles.fa[faId - 1] = w.focusareatitle;
});
if (!crwstatuses) {
titles._ = wishes.at(-1).updated.replace(/\D/g, '');
}
}
let expiry = 86400;
if (crwstatuses || crwcontinue) {
let prev = mw.storage.getObject('_EXPIRY_wishtitles');
if (prev) {
expiry = Math.round(Date.now() / 1000) + 86400 - prev;
}
}
mw.storage.setObject('wishtitles', titles, expiry);
crwcontinue = response?.continue?.crwcontinue;
if (crwcontinue) {
await updateTitles(crwstatuses, crwcontinue);
}
};
let getTitle = id => (
id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await $.get(
`https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html`
)));
await queryTitles(xfd, xfd.titleExtractor($page));
};
let queryCat = async (xfd, gcmcontinue) => {
let response = await new mw.Api().get({
action: 'query',
prop: 'info|categories',
inprop: 'watched',
clprop: 'sortkey',
clcategories: `Category:${xfd.cat}`,
generator: 'categorymembers',
gcmtitle: `Category:${xfd.cat}`,
gcmlimit: 'max',
gcmsort: 'timestamp',
gcmdir: 'older',
gcmcontinue: gcmcontinue,
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') {
xfd.pages.push(p.title);
}
});
if (response?.continue?.gcmcontinue) {
await queryCat(xfd, response.continue.gcmcontinue);
}
};
let show = async (xfd, lastId, isCache) => {
if (xfd.portlet && isCache) return;
let portletId = 'p-xfdnotifier-' + xfd.id;
if (xfd.portlet) {
$(xfd.portlet).find('ul').empty();
if (!xfd.pages.length) return;
} else {
await $.ready;
xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId);
}
let $label = $(`#${portletId}-label`).attr('title', xfd.full);
if (xfd.page) {
$label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page)));
}
xfd.pages.forEach(p => {
let t = mw.Title.newFromText(p);
let isTalk = t.isTalkPage();
let $other = $('<a>').attr({
href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(),
title: isTalk ? 'subject' : 'talk'
}).text(isTalk ? 's' : 't');
let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a');
$('<span>').addClass('xfdnotifier-sublinks').append(
$('<span>').append($other),
$('<span>').append(
$('<a>').attr({
href: t.getUrl({ action: 'history' }),
title: 'history'
}).text('h')
)
).insertAfter(link);
});
};
mw.hook('wikipage.content').add(mw.util.throttle(async () => {
let cache = mw.storage.getObject('xfdnotifier') || {};
let lastId = 'p-tb';
for (let xfd of xfds) {
let portletId = 'p-xfdnotifier-' + xfd.id;
let now = Math.floor(Date.now() / 1000);
if (now - cache[xfd.id]?.[0] < 600) {
xfd.pages = cache[xfd.id].slice(1);
await show(xfd, lastId, true);
lastId = portletId;
continue;
}
xfd.pages = [];
if (xfd.cat) {
await queryCat(xfd);
} else if (xfd.page) {
await queryPage(xfd);
}
cache[xfd.id] = [now, ...xfd.pages];
mw.storage.setObject('xfdnotifier', cache, 604800);
await show(xfd, lastId);
lastId = portletId;
}
}, 1800000));
})();
2vdfcrthn4ttjzn09hh62bxb521ss58
User:SongVĩ.Bot II
2
124239
735553
735481
2026-03-29T17:00:14Z
SongVĩ.Bot II
52414
[[User:SongVĩ.Bot II|Task 0]]: Đã 1553 ngày...
735553
wikitext
text/x-wiki
Cập nhật lần cuối: 30-03-2026
Đã 1553 ngày...
inwm84txkxwq8jn8tbizuahlnni9qvm
Wikipedia:Village pump/topic list
4
146208
735565
734466
2026-03-30T03:47:05Z
Cewbot
33876
[[User:Cewbot/log/20170915/configuration|Generate topic list: 5 topics]]
735565
wikitext
text/x-wiki
<!-- This page is auto-generated by bot. Please contact the bot operator to improve the tool. -->
{| class="wikitable sortable mw-collapsible" style="float:left;"
|-
! data-sort-type="number" style="font-weight: normal;" | <small>#</small> !! 💭 Title !! <span title="Count of comments">💬</span> !! <span title="Count of peoples in discussion">👥</span> !! 🙋 Last editor !! data-sort-type="isoDate" | <span title="Date/Time">🕒 <small>(UTC)</small></span>
|-
| style="text-align: right;" | 1
| [[Wikipedia:Village pump#Script|Script]]
| style="text-align: right;" | 8
| style="text-align: right;" | 5
| style="background-color: #bbb;" | [[User:LuniZunie|LuniZunie]]
| style="background-color: #bbb;" data-sort-type="isoDate" data-sort-value="2025-11-09T16:47:00.000Z" | 2025-11-09 <span style="color: blue;">16:47</span>
|-
| style="text-align: right;" | 2
| [[Wikipedia:Village pump#Report_concerning_Tanbiruzzammn|Report concerning Tanbiruzzammn]]
| style="text-align: right;" | 2
| style="text-align: right;" | 2
| style="background-color: #bbb;" | [[User:Barras|Barras]]
| style="background-color: #bbb;" data-sort-type="isoDate" data-sort-value="2025-12-09T21:45:00.000Z" | 2025-12-09 <span style="color: blue;">21:45</span>
|-
| style="text-align: right;" | 3
| [[Wikipedia:Village pump#Report_concerning_Bucheon|Report concerning Bucheon]]
| style="text-align: right;background-color: #fcc;" | 1
| style="text-align: right;background-color: #fcc;" | 1
| style="background-color: #bbb;" | [[User:PieWriter|PieWriter]]
| style="background-color: #bbb;" data-sort-type="isoDate" data-sort-value="2026-02-19T10:25:00.000Z" | 2026-02-19 <span style="color: blue;">10:25</span>
|-
| style="text-align: right;" | 4
| [[Wikipedia:Village pump#Versions_and_dates|Versions and dates]]
| style="text-align: right;" | 2
| style="text-align: right;background-color: #fcc;" | 1
| style="background-color: #ddd;" | [[Special:Contributions/~2026-13668-13|<span style="color: #c20;">~2026-13668-13</span>]]
| style="background-color: #ddd;" data-sort-type="isoDate" data-sort-value="2026-03-03T06:17:00.000Z" | 2026-03-03 <span style="color: blue;">06:17</span>
|-
| style="text-align: right;" | 5
| style="max-width: 24em" | <small>[[Wikipedia:Village pump#Upcoming_Wikimedia_Café_meetup_regarding_the_the_2026-2027_Wikimedia_Foundation_Annual_Plan|Upcoming Wikimedia Café meetup regarding the the 2026-2027 Wikimedia Foundation Annual Plan]]</small>
| style="text-align: right;background-color: #fcc;" | 1
| style="text-align: right;background-color: #fcc;" | 1
| style="background-color: #efe;" | [[User:Pine|Pine]]
| style="background-color: #efe;" data-sort-type="isoDate" data-sort-value="2026-03-30T03:46:00.000Z" | 2026-03-30 <span style="color: blue;">03:46</span>
|}
{| class="wikitable mw-collapsible mw-collapsed" style="float: left; margin-left: .5em;;{{#if:{{{no_time_legend|}}}|display:none;|}}"
! title="From the latest bot edit" | Legend
|-
| style="background-color: #efe;" |
* In the last hour
|-
| style="background-color: #eef;" |
* In the last day
|-
| |
* In the last week
|-
| style="background-color: #ddd;" |
* In the last month
|-
| style="background-color: #bbb;" |
* More than one month
|-
! Manual settings
|-
| style="max-width: 12em;" | <small>When exceptions occur,<br />please check [[User:Cewbot/log/20170915/configuration|the setting]] first.</small>
|-
|}
{{Clear}}
2qrk8fb08ltwfk47zzo6kcuqhhv2syi
735573
735565
2026-03-30T09:47:18Z
Cewbot
33876
[[User:Cewbot/log/20170915/configuration|Generate topic list: 5 topics]]
735573
wikitext
text/x-wiki
<!-- This page is auto-generated by bot. Please contact the bot operator to improve the tool. -->
{| class="wikitable sortable mw-collapsible" style="float:left;"
|-
! data-sort-type="number" style="font-weight: normal;" | <small>#</small> !! 💭 Title !! <span title="Count of comments">💬</span> !! <span title="Count of peoples in discussion">👥</span> !! 🙋 Last editor !! data-sort-type="isoDate" | <span title="Date/Time">🕒 <small>(UTC)</small></span>
|-
| style="text-align: right;" | 1
| [[Wikipedia:Village pump#Script|Script]]
| style="text-align: right;" | 8
| style="text-align: right;" | 5
| style="background-color: #bbb;" | [[User:LuniZunie|LuniZunie]]
| style="background-color: #bbb;" data-sort-type="isoDate" data-sort-value="2025-11-09T16:47:00.000Z" | 2025-11-09 <span style="color: blue;">16:47</span>
|-
| style="text-align: right;" | 2
| [[Wikipedia:Village pump#Report_concerning_Tanbiruzzammn|Report concerning Tanbiruzzammn]]
| style="text-align: right;" | 2
| style="text-align: right;" | 2
| style="background-color: #bbb;" | [[User:Barras|Barras]]
| style="background-color: #bbb;" data-sort-type="isoDate" data-sort-value="2025-12-09T21:45:00.000Z" | 2025-12-09 <span style="color: blue;">21:45</span>
|-
| style="text-align: right;" | 3
| [[Wikipedia:Village pump#Report_concerning_Bucheon|Report concerning Bucheon]]
| style="text-align: right;background-color: #fcc;" | 1
| style="text-align: right;background-color: #fcc;" | 1
| style="background-color: #bbb;" | [[User:PieWriter|PieWriter]]
| style="background-color: #bbb;" data-sort-type="isoDate" data-sort-value="2026-02-19T10:25:00.000Z" | 2026-02-19 <span style="color: blue;">10:25</span>
|-
| style="text-align: right;" | 4
| [[Wikipedia:Village pump#Versions_and_dates|Versions and dates]]
| style="text-align: right;" | 2
| style="text-align: right;background-color: #fcc;" | 1
| style="background-color: #ddd;" | [[Special:Contributions/~2026-13668-13|<span style="color: #c20;">~2026-13668-13</span>]]
| style="background-color: #ddd;" data-sort-type="isoDate" data-sort-value="2026-03-03T06:17:00.000Z" | 2026-03-03 <span style="color: blue;">06:17</span>
|-
| style="text-align: right;" | 5
| style="max-width: 24em" | <small>[[Wikipedia:Village pump#Upcoming_Wikimedia_Café_meetup_regarding_the_the_2026-2027_Wikimedia_Foundation_Annual_Plan|Upcoming Wikimedia Café meetup regarding the the 2026-2027 Wikimedia Foundation Annual Plan]]</small>
| style="text-align: right;background-color: #fcc;" | 1
| style="text-align: right;background-color: #fcc;" | 1
| style="background-color: #eef;" | [[User:Pine|Pine]]
| style="background-color: #eef;" data-sort-type="isoDate" data-sort-value="2026-03-30T03:46:00.000Z" | 2026-03-30 <span style="color: blue;">03:46</span>
|}
{| class="wikitable mw-collapsible mw-collapsed" style="float: left; margin-left: .5em;;{{#if:{{{no_time_legend|}}}|display:none;|}}"
! title="From the latest bot edit" | Legend
|-
| style="background-color: #efe;" |
* In the last hour
|-
| style="background-color: #eef;" |
* In the last day
|-
| |
* In the last week
|-
| style="background-color: #ddd;" |
* In the last month
|-
| style="background-color: #bbb;" |
* More than one month
|-
! Manual settings
|-
| style="max-width: 12em;" | <small>When exceptions occur,<br />please check [[User:Cewbot/log/20170915/configuration|the setting]] first.</small>
|-
|}
{{Clear}}
6i8titzyeg4kgwjb8suznc5uumx4xxs
User:Asked42/common.js
2
158658
735569
735476
2026-03-30T07:13:33Z
Asked42
58528
735569
javascript
text/javascript
/**
* English Wikinews - Modern Newsroom Dashboard v2.1
* Target: en.wikinews.org API (Cross-origin supported)
* Fix: Replaced bulk rvlimit queries with concurrent individual queries.
*/
(function () {
const API_URL = 'https://en.wikinews.org/w/api.php';
const WIKI_URL = 'https://en.wikinews.org/wiki/';
const INDEX_URL = 'https://en.wikinews.org/w/index.php';
const ICONS = {
info: 'https://upload.wikimedia.org/wikipedia/commons/3/3c/Codex_icon_info.svg',
edit: 'https://upload.wikimedia.org/wikipedia/commons/5/5c/Codex_icon_edit.svg',
history: 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Codex_icon_history.svg'
};
// 1. Inject Modern CSS (Header CSS removed)
const injectCSS = () => {
if (document.getElementById('newsroom-styles')) return;
const style = document.createElement('style');
style.id = 'newsroom-styles';
style.innerHTML = `
#newsroom-app { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 1200px; margin: 0 auto; color: #202122; line-height: 1.5; }
/* Grid & Sections */
.nr-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 20px; }
.nr-section { background: #fff; border: 1px solid #c8ccd1; border-radius: 6px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.nr-section h3 { margin-top: 0; border-bottom: 2px solid #eaecf0; padding-bottom: 10px; color: #202122; font-size: 1.3em; }
/* Lists & Items */
.nr-list { list-style-type: none; padding-left: 0; margin: 0; }
.nr-list li { padding: 12px 0; border-bottom: 1px solid #eaecf0; display: flex; justify-content: space-between; align-items: flex-start; gap: 15px;}
.nr-list li:last-child { border-bottom: none; padding-bottom: 0; }
.nr-article-info { flex-grow: 1; }
.nr-article-title { font-weight: 600; font-size: 1.05em; display: block; margin-bottom: 4px; }
.nr-article-title a { color: #0645ad; text-decoration: none; }
.nr-article-title a:hover { text-decoration: underline; }
.nr-meta { font-size: 0.85em; color: #54595d; display: block; }
/* Actions & Tags */
.nr-actions { display: flex; gap: 8px; flex-shrink: 0; background: #f8f9fa; padding: 4px 8px; border-radius: 4px; border: 1px solid #eaecf0; }
.nr-icon { width: 18px; height: 18px; opacity: 0.6; transition: opacity 0.2s; cursor: pointer; display: block; }
.nr-icon:hover { opacity: 1; }
.nr-tag { font-size: 0.75em; padding: 2px 8px; border-radius: 12px; font-weight: bold; margin-left: 8px; vertical-align: middle; }
.nr-tag.urgent { background: #d33; color: white; }
.nr-tag.breaking { background: #ed8700; color: white; }
/* States */
.nr-loading { color: #72777d; display: flex; align-items: center; gap: 8px; font-style: italic; }
.nr-empty { color: #54595d; text-align: center; padding: 20px; background: #f8f9fa; border-radius: 4px; border: 1px dashed #c8ccd1; }
.nr-error { color: #d33; padding: 10px; background: #fee7e6; border-radius: 4px; font-size: 0.9em; margin-bottom: 10px; }
`;
document.head.appendChild(style);
};
// 2. API Helper
const fetchAPI = async (params) => {
const url = new URL(API_URL);
url.search = new URLSearchParams({ ...params, format: 'json', origin: '*' });
const res = await fetch(url);
const data = await res.json();
if (data.error) throw new Error(data.error.info);
return data;
};
const timeAgo = (timestamp) => {
if (!timestamp) return 'Unknown time';
const diff = Date.now() - new Date(timestamp).getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24);
if (days === 0 && hours === 0) return 'Just now';
return `${days}d ${hours}h ago`;
};
// 3. UI Builders
const buildActions = (title) => {
const encTitle = encodeURIComponent(title);
return `
<div class="nr-actions">
<a href="${INDEX_URL}?title=${encTitle}&action=info" target="_blank" title="Info"><img src="${ICONS.info}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=edit" target="_blank" title="Edit"><img src="${ICONS.edit}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=history" target="_blank" title="History"><img src="${ICONS.history}" class="nr-icon"></a>
</div>
`;
};
// 4. Section Loaders
const loadSection1 = async () => {
const el = document.getElementById('sect-1');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Developing', cmnamespace: 0, cmlimit: 50 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let catData = await fetchAPI({ action: 'query', prop: 'categories', titles: titles.join('|'), cllimit: 'max' });
let exclusions = ['Category:Published', 'Category:Prepared stories', 'Category:No publish', 'Category:Brief', 'Category:Disputed'];
let filtered = [];
for (let pageId in catData.query.pages) {
let page = catData.query.pages[pageId];
let pageCats = page.categories ? page.categories.map(c => c.title) : [];
if (!pageCats.some(c => exclusions.includes(c))) filtered.push(page.title);
}
if (!filtered.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let html = '<ul class="nr-list">';
filtered.forEach(t => {
html += `<li><div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>${buildActions(t)}</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 1 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Drafts: ${e.message}</div>`;
}
};
const loadSection2 = async () => {
const el = document.getElementById('sect-2');
try {
const cats = ['Category:Review', 'Category:Breaking_review', 'Category:Urgent_review'];
let allMembers = [];
for (let cat of cats) {
let res = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
res.query.categorymembers.forEach(m => allMembers.push({ title: m.title, type: cat }));
}
if (!allMembers.length) return el.innerHTML = '<div class="nr-empty">Queue is empty! No articles waiting for review.</div>';
let uniqueTitles = [...new Set(allMembers.map(m => m.title))].slice(0, 45);
// FIX: Fetch history for each title individually to allow rvlimit=5
const historyPromises = uniqueTitles.map(async (title) => {
try {
return await fetchAPI({ action: 'query', prop: 'revisions', titles: title, rvprop: 'user|timestamp|comment', rvlimit: 5 });
} catch (e) { return null; }
});
const historyResults = await Promise.all(historyPromises);
const triggers = ["submitting article for review", "submit for review", "re submit", "please review"];
let html = '<ul class="nr-list">';
for (let histData of historyResults) {
if (!histData || !histData.query || !histData.query.pages) continue;
let pageId = Object.keys(histData.query.pages)[0];
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
// Fallback to the latest edit if we don't find the exact comment
let match = revs.find(r => r.comment && triggers.some(t => r.comment.toLowerCase().includes(t))) || revs[0] || { user: 'Unknown', timestamp: null };
let catInfo = allMembers.find(m => m.title === page.title);
let tag = '';
if (catInfo && catInfo.type.includes('Breaking')) tag = '<span class="nr-tag breaking">Breaking</span>';
if (catInfo && catInfo.type.includes('Urgent')) tag = '<span class="nr-tag urgent">Urgent</span>';
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a> ${tag}</span>
<span class="nr-meta">Submitted by: <strong>${match.user}</strong> | ${timeAgo(match.timestamp)}</span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 2 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Review Queue: ${e.message}</div>`;
}
};
const loadSection3 = async () => {
const el = document.getElementById('sect-3');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Disputed', cmnamespace: 0, cmlimit: 20 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No disputed articles. Good job!</div>';
let safeTitles = titles.slice(0, 45);
// FIX: Fetch history for each title individually to allow rvlimit=5
const historyPromises = safeTitles.map(async (title) => {
try {
return await fetchAPI({ action: 'query', prop: 'revisions', titles: title, rvprop: 'user|timestamp|comment', rvlimit: 5 });
} catch (e) { return null; }
});
const historyResults = await Promise.all(historyPromises);
let html = '<ul class="nr-list">';
for (let histData of historyResults) {
if (!histData || !histData.query || !histData.query.pages) continue;
let pageId = Object.keys(histData.query.pages)[0];
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
let match = revs.find(r => r.comment && r.comment.toLowerCase().includes('needs improvement')) || revs[0] || { user: 'Unknown' };
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a></span>
<span class="nr-meta">Last Peer reviewed: <strong>${match.user}</strong></span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 3 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Disputed: ${e.message}</div>`;
}
};
const loadStandardCategories = async (containerId, categories) => {
const el = document.getElementById(containerId);
try {
let allTitles = [];
for (let cat of categories) {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
data.query.categorymembers.forEach(m => allTitles.push(m.title));
}
allTitles = [...new padding(allTitles)];
if (!allTitles.length) return el.innerHTML = '<div class="nr-empty">No articles found in this category.</div>';
let html = '<ul class="nr-list">';
allTitles.forEach(t => {
html += `<li>
<div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>
${buildActions(t)}
</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error(`Standard Cat Error (${containerId}):`, e);
el.innerHTML = `<div class="nr-error">Error loading category data: ${e.message}</div>`;
}
};
// 5. App Initialization
const initApp = () => {
const appRoot = document.getElementById('newsroom-app');
if (!appRoot) return;
// Clear root to prevent duplicates
appRoot.innerHTML = '';
injectCSS();
// Create Grid Container
const gridContainer = document.createElement('div');
gridContainer.className = 'nr-grid';
gridContainer.id = 'nr-sections-container';
appRoot.appendChild(gridContainer);
const sections = [
{ id: 'sect-1', title: 'Developing Drafts' },
{ id: 'sect-2', title: 'Submitted for Review' },
{ id: 'sect-3', title: 'Marked for Re-development' },
{ id: 'sect-4', title: 'Collaboration Requests' },
{ id: 'sect-5', title: 'Abandoned or Stale' }
];
sections.forEach(s => {
const box = document.createElement('div');
box.className = 'nr-section';
box.innerHTML = `<h3>${s.title}</h3><div id="${s.id}"><span class="nr-loading"><img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" width="16"> Loading...</span></div>`;
gridContainer.appendChild(box);
});
// Fire API calls in parallel
loadSection1();
loadSection2();
loadSection3();
loadStandardCategories('sect-4', ['Category:Article assistance requested', 'Category:Wikinewsies looking for help', 'Category:Ready']);
loadStandardCategories('sect-5', ['Category:Abandoned', 'Category:Stale']);
};
// Execute
initApp();
})();
40pmwc6xmz6vu6eh9h4xomza9hpyd5l
735570
735569
2026-03-30T07:14:22Z
Asked42
58528
Undid revision [[Special:Diff/735569|735569]] by [[Special:Contributions/Asked42|Asked42]] ([[User talk:Asked42|talk]])
735570
javascript
text/javascript
// This script provides the functionality for the "Perform Pre-Review" action.
// It is designed to be dynamically loaded by the main [[MediaWiki:SubmitWizard.js]] script.
// <nowiki>
( function () {
'use strict';
window.PreReviewScript = window.PreReviewScript || {};
window.PreReviewScript.triggerPreReviewAction = function() {
if ( window.PreReviewScript.mounted ) {
window.PreReviewScript.showForm();
return;
}
mw.loader.using( [ '@wikimedia/codex', 'mediawiki.api', 'mediawiki.util' ] ).then( function( require ) {
const Vue = require( 'vue' );
const Codex = require( '@wikimedia/codex' );
const pageTitle = mw.config.get( 'wgPageName' );
const currentOldId = mw.config.get( 'wgRevisionId' ) || mw.config.get( 'wgCurRevisionId' );
const contentDiv = document.getElementById( 'mw-content-text' );
if ( !contentDiv ) {
mw.notify( 'Could not find content area to mount form.', { type: 'error' } );
return;
}
let mountPoint = document.getElementById( 'prereview-form-mount' );
if ( !mountPoint ) {
mountPoint = document.createElement( 'div' );
mountPoint.id = 'prereview-form-mount';
contentDiv.insertBefore( mountPoint, contentDiv.firstChild );
}
const app = Vue.createMwApp( {
data: function() {
const now = new Date();
const formattedDate = now.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
const formattedTime = now.toLocaleTimeString('en-US', { hour12: false });
return {
showForm: false,
isExpanded: true,
isSubmitting: false,
reviewedVersion: currentOldId || '',
timestamp: formattedDate + ', ' + formattedTime,
// Review Criteria
copyright: 'not_reviewed',
newsworthy: 'not_reviewed',
style: 'not_reviewed',
npov: 'not_reviewed',
verifiable: 'not_reviewed',
// Conditional Copyvio
copyvioDescription: '',
// Notes
authorNotes: '',
reviewerNotes: '',
reviewOptions: [
{ label: 'Not reviewed', value: 'not_reviewed' },
{ label: 'Pass', value: 'pass' },
{ label: 'Fail', value: 'fail' }
]
};
},
computed: {
calculatedStatus: function() {
const criteria = [this.copyright, this.newsworthy, this.style, this.npov, this.verifiable];
const allPass = criteria.every( val => val === 'pass' );
return allPass ? 'pass' : 'flag';
}
},
template: `
<div v-if="showForm" class="skin-invert" style="border: 2px solid #0645ad; border-radius: 2px; margin: 20px 0; background: #ffffff; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden;">
<div @click="isExpanded = !isExpanded" style="background: linear-gradient(135deg, #0645ad 0%, #0b7fcc 100%); color: white; padding: 12px 20px; margin: 0; font-size: 1.2em; font-weight: 600; display: flex; justify-content: space-between; align-items: center; cursor: pointer;">
<span>Perform a pre review using submit wizard</span>
<div style="display: flex; gap: 16px; align-items: center;">
<img v-if="isExpanded" src="https://upload.wikimedia.org/wikipedia/commons/1/10/OOjs_UI_icon_collapse.svg" style="filter: invert(1); width: 20px; height: 20px;" alt="Collapse" title="Collapse Form" />
<img v-else src="https://upload.wikimedia.org/wikipedia/commons/9/90/OOjs_UI_icon_expand.svg" style="filter: invert(1); width: 20px; height: 20px;" alt="Expand" title="Expand Form" />
<a href="/wiki/Template:Pre-review/doc" target="_blank" @click.stop style="display: flex; align-items: center;">
<img src="https://upload.wikimedia.org/wikipedia/commons/7/7c/Codex_icon_help.svg" style="filter: invert(1); width: 20px; height: 20px;" alt="Help" title="View Documentation" />
</a>
</div>
</div>
<div v-show="isExpanded" style="padding: 20px; background: #ffffff;">
<div style="margin-bottom: 24px; font-size: 1.1em;">
You are pre reviewing the <strong>{{ reviewedVersion }}</strong> revision of this article at <strong>{{ timestamp }}</strong>.
</div>
<div style="font-weight: bold; margin-bottom: 16px; font-size: 1.2em; border-bottom: 1px solid #eaecf0; padding-bottom: 8px;">Evaluation Criteria</div>
<div style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 32px;">
<div style="display: flex; align-items: center; gap: 16px;">
<div style="font-weight: 600; width: 150px;">Copyright</div>
<div style="width: 250px;">
<cdx-select v-model:selected="copyright" :menu-items="reviewOptions" style="width: 100%;"></cdx-select>
</div>
<cdx-button @click="openCopyvioTool">Check Copyvio</cdx-button>
</div>
<div v-if="copyright === 'fail'" style="margin-left: 166px; border-left: 3px solid #d73333; padding-left: 16px; display: flex; align-items: center; gap: 8px;">
<label style="font-weight: 600; white-space: nowrap;">Describe copyvio:</label>
<cdx-text-input v-model="copyvioDescription" placeholder="Provide details of the copyright infringement" style="flex-grow: 1;"></cdx-text-input>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<div style="font-weight: 600; width: 150px;">Newsworthiness</div>
<div style="width: 250px;">
<cdx-select v-model:selected="newsworthy" :menu-items="reviewOptions" style="width: 100%;"></cdx-select>
</div>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<div style="font-weight: 600; width: 150px;">Style Guide</div>
<div style="width: 250px;">
<cdx-select v-model:selected="style" :menu-items="reviewOptions" style="width: 100%;"></cdx-select>
</div>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<div style="font-weight: 600; width: 150px;">NPOV</div>
<div style="width: 250px;">
<cdx-select v-model:selected="npov" :menu-items="reviewOptions" style="width: 100%;"></cdx-select>
</div>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<div style="font-weight: 600; width: 150px;">Verifiability</div>
<div style="width: 250px;">
<cdx-select v-model:selected="verifiable" :menu-items="reviewOptions" style="width: 100%;"></cdx-select>
</div>
</div>
</div>
<div style="font-weight: bold; margin-bottom: 16px; font-size: 1.2em; border-bottom: 1px solid #eaecf0; padding-bottom: 8px;">Notes</div>
<div style="display: flex; gap: 24px; margin-bottom: 24px;">
<div style="flex: 1;">
<cdx-field>
<template #label>Any Notes for the Author?</template>
<cdx-text-area v-model="authorNotes" placeholder="Notes addressed to the author(s)" rows="4"></cdx-text-area>
</cdx-field>
</div>
<div style="width: 1px; background-color: #c8ccd1;"></div>
<div style="flex: 1;">
<cdx-field>
<template #label>Any Notes for the Reviewer?</template>
<cdx-text-area v-model="reviewerNotes" placeholder="Notes addressed to the reviewer" rows="4"></cdx-text-area>
</cdx-field>
</div>
</div>
<div style="display: flex; gap: 8px; justify-content: flex-end; margin-bottom: 12px;">
<cdx-button @click="closeForm">Cancel</cdx-button>
<cdx-button action="progressive" weight="primary" @click="submitPreReview" :disabled="isSubmitting">
{{ isSubmitting ? 'Submitting...' : 'Add Pre Review' }}
</cdx-button>
</div>
<div v-if="isSubmitting" style="width: 100%;">
<cdx-progress-bar inline></cdx-progress-bar>
</div>
</div>
</div>
`,
methods: {
closeForm: function() {
this.showForm = false;
},
openCopyvioTool: function() {
const url = 'https://copyvios.toolforge.org/?lang=en&project=wikinews&title=' +
encodeURIComponent( mw.config.get( 'wgPageName' ) ) +
'&oldid=&action=search&use_engine=1&use_links=1&turnitin=0';
window.open( url, '_blank' );
},
submitPreReview: function() {
const self = this;
if ( !this.reviewedVersion ) {
mw.notify( 'Reviewed Version is required!', { type: 'error' } );
return;
}
// Validation Logic
const criteria = [this.copyright, this.newsworthy, this.style, this.npov, this.verifiable];
const hasReviewedOne = criteria.some( val => val !== 'not_reviewed' );
const hasNotes = (this.authorNotes && this.authorNotes.trim().length > 0) ||
(this.reviewerNotes && this.reviewerNotes.trim().length > 0);
if (!hasReviewedOne) {
mw.notify('Please evaluate at least one criterion before submitting.', { type: 'error' });
return;
}
if (!hasNotes) {
mw.notify('Please provide notes for either the author or the reviewer before submitting.', { type: 'error' });
return;
}
this.isSubmitting = true;
const api = new mw.Api();
const talkPageTitle = new mw.Title( pageTitle ).getTalkPage().getPrefixedText();
const articleUrl = 'https://en.wikinews.org/w/index.php?title=' +
encodeURIComponent( pageTitle ) + '&oldid=' + this.reviewedVersion;
let formattedCopyright = this.copyright === 'not_reviewed' ? '' : this.copyright;
if ( this.copyright === 'fail' && this.copyvioDescription.trim() !== '' ) {
formattedCopyright = 'fail - ' + this.copyvioDescription;
}
const formatCriteria = (val) => val === 'not_reviewed' ? '' : val;
const userName = mw.config.get( 'wgUserName' ) || 'Unknown User';
const templateText = '== Pre Review by ' + userName + ' ==\n\n' +
'{{Pre-review\n' +
'|evaluated-url=' + articleUrl + '\n' +
'|status=' + this.calculatedStatus + '\n' +
'|updated=\n' +
'|copyright=' + formattedCopyright + '\n' +
'|earwig-permalink=\n' +
'|newsworthy=' + formatCriteria(this.newsworthy) + '\n' +
'|verifiable=' + formatCriteria(this.verifiable) + '\n' +
'|npov=' + formatCriteria(this.npov) + '\n' +
'|style=' + formatCriteria(this.style) + '\n' +
'|author-notes=' + this.authorNotes + '\n' +
'|reviewer-notes=' + this.reviewerNotes + '\n' +
'|updated-notes=\n' +
'}}\n\n' +
'--' + '~~' + '~~';
api.get( {
action: 'query',
titles: talkPageTitle,
prop: 'info'
} ).then( function( data ) {
const pages = data.query.pages;
const pageId = Object.keys( pages )[0];
const pageExists = pageId !== '-1';
return api.postWithToken( 'csrf', {
action: 'edit',
title: talkPageTitle,
appendtext: pageExists ? '\n\n' + templateText : templateText,
summary: 'Adding Pre-Review (via [[MediaWiki:SubmitWizard.js/doc|SubmitWizard]])',
createonly: !pageExists
} );
} ).then( function() {
mw.notify( 'Pre review has been succesfully added to the articles talk page', { type: 'success' } );
self.isSubmitting = false;
self.closeForm();
setTimeout( function() {
window.open( mw.util.getUrl( talkPageTitle ), '_blank' );
}, 1500 );
} ).catch( function( error ) {
console.error( 'Error submitting pre-review:', error );
mw.notify( 'Error submitting pre-review: ' + error, { type: 'error' } );
self.isSubmitting = false;
} );
}
},
mounted: function() {
window.PreReviewScript.showForm = function() {
this.showForm = true;
this.isExpanded = true;
}.bind( this );
}
} );
app.component( 'cdx-button', Codex.CdxButton );
app.component( 'cdx-field', Codex.CdxField );
app.component( 'cdx-text-input', Codex.CdxTextInput );
app.component( 'cdx-text-area', Codex.CdxTextArea );
app.component( 'cdx-select', Codex.CdxSelect );
app.component( 'cdx-progress-bar', Codex.CdxProgressBar );
const instance = app.mount( mountPoint );
window.PreReviewScript.instance = instance;
window.PreReviewScript.mounted = true;
window.PreReviewScript.showForm = function() {
instance.showForm = true;
instance.isExpanded = true;
};
instance.showForm = true;
} ).catch( function( error ) {
console.error( 'Error loading Codex:', error );
mw.notify( 'Error loading Pre-Review form. Please try again.', { type: 'error' } );
} );
};
mw.loader.using( 'mediawiki.util' ).then( function () {
const portletLink = mw.util.addPortletLink(
'p-tb',
'#',
'Perform Pre-Review',
't-prereview',
'Open the Pre-Review form for this page'
);
if ( portletLink ) {
portletLink.addEventListener( 'click', function ( e ) {
e.preventDefault();
window.PreReviewScript.triggerPreReviewAction();
} );
}
} );
}() );
// </nowiki>
var extraCSS = mw.util.getParamValue( 'withCSS' ),
extraJS = mw.util.getParamValue( 'withJSS' );
if ( extraCSS ) {
if ( extraCSS.match( /^User:[^&<>=%#]*\.css$/ ) ) {
mw.loader.load( '/w/index.php?title=' + extraCSS + '&action=raw&ctype=text/css', 'text/css' );
} else {
mw.notify( 'Only pages from the MediaWiki namespace are allowed.', { title: 'Invalid withCSS value' } );
}
}
if ( extraJS ) {
if ( extraJS.match( /^User:[^&<>=%#]*\.js$/ ) ) {
mw.loader.load( '/w/index.php?title=' + extraJS + '&action=raw&ctype=text/javascript' );
} else {
mw.notify( 'Only pages from the MediaWiki namespace are allowed.', { title: 'Invalid withJS value' } );
}
}
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Asked42/sandbox2.js&action=raw&ctype=text/javascript');
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Asked42/sandbox1.js&action=raw&ctype=text/javascript');
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Asked42/Experiment3.js&action=raw&ctype=text/javascript');
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Asked42/Experiment.js&action=raw&ctype=text/javascript');
bmiaq3x34eimeju61m50qkhn6tbc2d6
User:Asked42/sandbox1
2
158659
735548
735111
2026-03-29T15:28:20Z
Asked42
58528
735548
wikitext
text/x-wiki
<div style="font-family:Linux Libertine,Georgia,Times,serif;margin:0 0 0 0;padding:0;">
<div style="background:linear-gradient(135deg,#00528c 0%,#003f6e 60%,#002a4a 100%);padding:22px 28px 18px 28px;border-radius:4px 4px 0 0;border-bottom:3px solid #f8f9fa;position:relative;overflow:hidden;">
<div style="position:absolute;top:0;right:0;width:220px;height:100%;background:url('https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Wikinews-logo-en.svg/120px-Wikinews-logo-en.svg.png') no-repeat right 18px center / 90px auto;opacity:0.13;pointer-events:none;"></div>
<div style="display:inline-block;vertical-align:middle;">
<div style="color:#ffffff;font-size:11px;letter-spacing:2.5px;text-transform:uppercase;font-family:Arial,Helvetica,sans-serif;font-weight:400;opacity:0.78;margin-bottom:4px;">English Wikinews · Online Event</div>
<div style="color:#ffffff;font-size:28px;font-weight:700;letter-spacing:0.3px;line-height:1.1;text-shadow:0 2px 8px rgba(0,0,0,0.25);">Wikinews Reviewer Workshop 2026</div>
<div style="color:#a8d4f0;font-size:13px;margin-top:7px;font-family:Arial,Helvetica,sans-serif;font-weight:400;">A collaborative online workshop for reviewers and contributors</div>
</div>
</div>
<div style="background:#f8f9fa;border:1px solid #a2a9b1;border-top:none;border-radius:0 0 4px 4px;padding:0 16px;display:flex;gap:0;align-items:flex-end;min-height:44px;flex-wrap:wrap;">
<div style="display:inline-block;padding:12px 22px 11px 22px;font-family:Arial,Helvetica,sans-serif;font-size:13.5px;font-weight:700;color:#ffffff;background:#00528c;border-left:1px solid #00528c;border-right:1px solid #00528c;border-top:2px solid #00528c;border-radius:3px 3px 0 0;margin-top:6px;margin-right:2px;cursor:pointer;letter-spacing:0.1px;">Workshop Sections</div>
<div style="display:inline-block;padding:12px 22px 11px 22px;font-family:Arial,Helvetica,sans-serif;font-size:13.5px;font-weight:400;color:#0645ad;background:#ffffff;border-left:1px solid #a2a9b1;border-right:1px solid #a2a9b1;border-top:2px solid #a2a9b1;border-radius:3px 3px 0 0;margin-top:6px;margin-right:2px;cursor:pointer;letter-spacing:0.1px;">Projects</div>
<div style="display:inline-block;padding:12px 22px 11px 22px;font-family:Arial,Helvetica,sans-serif;font-size:13.5px;font-weight:400;color:#0645ad;background:#ffffff;border-left:1px solid #a2a9b1;border-right:1px solid #a2a9b1;border-top:2px solid #a2a9b1;border-radius:3px 3px 0 0;margin-top:6px;cursor:pointer;letter-spacing:0.1px;">Resources</div>
</div>
</div>
== Januyary 2026 ==
=== Blast Open Rotterdam enters playoffs, 6 teams advance ===
:{| id="123456" class="wikitable"
|+ Review of revision 123456
|-
! Reviewed by
| Reviewer Name
|-
! Timestamp
| 24 March 2026, 21:00 (UTC)
|-
! Status
| Published
|-
! Reviewer's comment
| This revision meets all editorial guidelines and is suitable for publication.
|}
:{| class="wikitable"
|+ Review of revision 123456
|-
! Reviewed by
| [[User:ExampleReviewer|ExampleReviewer]]
|-
! Timestamp
| 23 March 2026, 14:32 (UTC)
|-
! Status
| Rejected
|-
! Reviewer's comment
| The article requires additional sources and neutrality improvements before it can be published.
|}
=== Musique Libre Femmes plays for Women's History Month at Tompkins Square Library in New York City ===
:{| class="wikitable"
|+ Review of revision 123456
|-
! Reviewed by
| Reviewer Name
|-
! Timestamp
| 24 March 2026, 21:00 (UTC)
|-
! Status
| Published
|-
! Reviewer's comment
| This revision meets all editorial guidelines and is suitable for publication.
|}
[[Erna Solberg]]
{{Tasks|src|mos|re-review}}
<div id="review"> </div>
<div id="wikidialog-comment-section-v2"> </div>
<span id="context-ref-1" data-type="text" data-revid="732733" style="display:none;">{{{Lorem Ipsum is simply dummy text of the printing and typesetting industry.}}} {{{ consectetur, from a Lorem Ipsum passage,}}}</span>
<span id="context-ref-2" data-type="diff" style="display:none;">{{{732733}}} {{{732727}}}</span>
== Context Referencing ==
Context Referencing allows you to attach a reference to a specific statement in your peer review comment. A context can be either a set of highlighted phrases from a particular revision of the article, or a diff comparison between two revision IDs. This helps readers understand exactly which part of the article you are referring to in your comment.
=== Why Use Context Referencing? ===
It is easiest to understand through an example. Suppose your peer review comment reads:
{{quote|I have removed some unsourced phrases from the article, and made some minor copyedit corrections related to grammar and style.}}
As written, this comment does not specify ''which'' phrases were removed or ''what'' copyedits were made. Context Referencing solves this by letting you attach supporting references directly to those statements. With contexts added, the same comment would look like:
{{quote|I have removed some unsourced phrases from the article [context1], and made some minor copyedit corrections related to grammar and style [context2].}}
Here, '''[context1]''' is a text-based reference and '''[context2]''' is a diff-based reference. These labels become clickable once the review is submitted:
* Clicking '''[context1]''' opens a specific revision of the article with your referenced phrases highlighted, so the reader can see exactly which text you are referring to.
* Clicking '''[context2]''' opens a diff view between the two revision IDs you provided, showing precisely what changes were made.
=== How to Add a Context ===
# Click the '''CR''' button. A dialog box will open.
# Select a '''reference type'''. Two types are available:
==== Text-Based Reference ====
Use this to highlight specific phrases from a revision of the article.
# Enter the '''revision ID''' from the article's history that you want to reference.
# Add the specific '''phrases or text''' from that revision. You can add multiple phrases.
==== Diff-Based Reference ====
Use this to show a comparison between two versions of the article.
# Enter the '''two revision IDs''' between which you want to generate a diff.
Once you have filled in the required fields, click '''Save'''. The context label (e.g. <code>[context1]</code>, <code>[context2]</code>) will be inserted into the comment text area in sequential order.
Context labels are not clickable while you are drafting the review. They become clickable for readers after the review has been submitted.
----
[[Lorem Ipsum]] is simply dummy text of the [[main page|printing]] and typesetting industry. {{w|Lorem Ipsum}} has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type [[specimen book]]. '''It has''' survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ''It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.''
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
ete20e5r07brbqw1j19574flfva6ed1
735567
735548
2026-03-30T07:01:59Z
Asked42
58528
735567
wikitext
text/x-wiki
<div id="newsroom-app"></div>
<div style="font-family:Linux Libertine,Georgia,Times,serif;margin:0 0 0 0;padding:0;">
<div style="background:linear-gradient(135deg,#00528c 0%,#003f6e 60%,#002a4a 100%);padding:22px 28px 18px 28px;border-radius:4px 4px 0 0;border-bottom:3px solid #f8f9fa;position:relative;overflow:hidden;">
<div style="position:absolute;top:0;right:0;width:220px;height:100%;background:url('https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Wikinews-logo-en.svg/120px-Wikinews-logo-en.svg.png') no-repeat right 18px center / 90px auto;opacity:0.13;pointer-events:none;"></div>
<div style="display:inline-block;vertical-align:middle;">
<div style="color:#ffffff;font-size:11px;letter-spacing:2.5px;text-transform:uppercase;font-family:Arial,Helvetica,sans-serif;font-weight:400;opacity:0.78;margin-bottom:4px;">English Wikinews · Online Event</div>
<div style="color:#ffffff;font-size:28px;font-weight:700;letter-spacing:0.3px;line-height:1.1;text-shadow:0 2px 8px rgba(0,0,0,0.25);">Wikinews Reviewer Workshop 2026</div>
<div style="color:#a8d4f0;font-size:13px;margin-top:7px;font-family:Arial,Helvetica,sans-serif;font-weight:400;">A collaborative online workshop for reviewers and contributors</div>
</div>
</div>
<div style="background:#f8f9fa;border:1px solid #a2a9b1;border-top:none;border-radius:0 0 4px 4px;padding:0 16px;display:flex;gap:0;align-items:flex-end;min-height:44px;flex-wrap:wrap;">
<div style="display:inline-block;padding:12px 22px 11px 22px;font-family:Arial,Helvetica,sans-serif;font-size:13.5px;font-weight:700;color:#ffffff;background:#00528c;border-left:1px solid #00528c;border-right:1px solid #00528c;border-top:2px solid #00528c;border-radius:3px 3px 0 0;margin-top:6px;margin-right:2px;cursor:pointer;letter-spacing:0.1px;">Workshop Sections</div>
<div style="display:inline-block;padding:12px 22px 11px 22px;font-family:Arial,Helvetica,sans-serif;font-size:13.5px;font-weight:400;color:#0645ad;background:#ffffff;border-left:1px solid #a2a9b1;border-right:1px solid #a2a9b1;border-top:2px solid #a2a9b1;border-radius:3px 3px 0 0;margin-top:6px;margin-right:2px;cursor:pointer;letter-spacing:0.1px;">Projects</div>
<div style="display:inline-block;padding:12px 22px 11px 22px;font-family:Arial,Helvetica,sans-serif;font-size:13.5px;font-weight:400;color:#0645ad;background:#ffffff;border-left:1px solid #a2a9b1;border-right:1px solid #a2a9b1;border-top:2px solid #a2a9b1;border-radius:3px 3px 0 0;margin-top:6px;cursor:pointer;letter-spacing:0.1px;">Resources</div>
</div>
</div>
== Januyary 2026 ==
=== Blast Open Rotterdam enters playoffs, 6 teams advance ===
:{| id="123456" class="wikitable"
|+ Review of revision 123456
|-
! Reviewed by
| Reviewer Name
|-
! Timestamp
| 24 March 2026, 21:00 (UTC)
|-
! Status
| Published
|-
! Reviewer's comment
| This revision meets all editorial guidelines and is suitable for publication.
|}
:{| class="wikitable"
|+ Review of revision 123456
|-
! Reviewed by
| [[User:ExampleReviewer|ExampleReviewer]]
|-
! Timestamp
| 23 March 2026, 14:32 (UTC)
|-
! Status
| Rejected
|-
! Reviewer's comment
| The article requires additional sources and neutrality improvements before it can be published.
|}
=== Musique Libre Femmes plays for Women's History Month at Tompkins Square Library in New York City ===
:{| class="wikitable"
|+ Review of revision 123456
|-
! Reviewed by
| Reviewer Name
|-
! Timestamp
| 24 March 2026, 21:00 (UTC)
|-
! Status
| Published
|-
! Reviewer's comment
| This revision meets all editorial guidelines and is suitable for publication.
|}
[[Erna Solberg]]
{{Tasks|src|mos|re-review}}
<div id="review"> </div>
<div id="wikidialog-comment-section-v2"> </div>
<span id="context-ref-1" data-type="text" data-revid="732733" style="display:none;">{{{Lorem Ipsum is simply dummy text of the printing and typesetting industry.}}} {{{ consectetur, from a Lorem Ipsum passage,}}}</span>
<span id="context-ref-2" data-type="diff" style="display:none;">{{{732733}}} {{{732727}}}</span>
== Context Referencing ==
Context Referencing allows you to attach a reference to a specific statement in your peer review comment. A context can be either a set of highlighted phrases from a particular revision of the article, or a diff comparison between two revision IDs. This helps readers understand exactly which part of the article you are referring to in your comment.
=== Why Use Context Referencing? ===
It is easiest to understand through an example. Suppose your peer review comment reads:
{{quote|I have removed some unsourced phrases from the article, and made some minor copyedit corrections related to grammar and style.}}
As written, this comment does not specify ''which'' phrases were removed or ''what'' copyedits were made. Context Referencing solves this by letting you attach supporting references directly to those statements. With contexts added, the same comment would look like:
{{quote|I have removed some unsourced phrases from the article [context1], and made some minor copyedit corrections related to grammar and style [context2].}}
Here, '''[context1]''' is a text-based reference and '''[context2]''' is a diff-based reference. These labels become clickable once the review is submitted:
* Clicking '''[context1]''' opens a specific revision of the article with your referenced phrases highlighted, so the reader can see exactly which text you are referring to.
* Clicking '''[context2]''' opens a diff view between the two revision IDs you provided, showing precisely what changes were made.
=== How to Add a Context ===
# Click the '''CR''' button. A dialog box will open.
# Select a '''reference type'''. Two types are available:
==== Text-Based Reference ====
Use this to highlight specific phrases from a revision of the article.
# Enter the '''revision ID''' from the article's history that you want to reference.
# Add the specific '''phrases or text''' from that revision. You can add multiple phrases.
==== Diff-Based Reference ====
Use this to show a comparison between two versions of the article.
# Enter the '''two revision IDs''' between which you want to generate a diff.
Once you have filled in the required fields, click '''Save'''. The context label (e.g. <code>[context1]</code>, <code>[context2]</code>) will be inserted into the comment text area in sequential order.
Context labels are not clickable while you are drafting the review. They become clickable for readers after the review has been submitted.
----
[[Lorem Ipsum]] is simply dummy text of the [[main page|printing]] and typesetting industry. {{w|Lorem Ipsum}} has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type [[specimen book]]. '''It has''' survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ''It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.''
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
nkpnp95v504qw3z1v0d4wmzvrwzdcjj
User:Asked42/sandbox2
2
162515
735552
732731
2026-03-29T17:00:01Z
Asked42
58528
735552
wikitext
text/x-wiki
<div style="margin-bottom:1.2em;"><div style="background:#00528c;color:#ffffff;font-size:16px;font-weight:700;padding:6px 12px;border:1px solid #00408e;">What is a Reviewer?</div>
<div style="background:#ffffff;border:1px solid #a2a9b1;border-top:none;padding:14px 16px;font-size:15px;line-height:1.6;">
A reviewer is responsible for evaluating whether an article meets Wikinews standards before publication. <br/>
'''A reviewer does not write the article; they ensure it is ready to be published.'''
</div></div>
ksrp6rfe1zrt7ihaf6yiucwhys6620m
735554
735552
2026-03-29T17:29:00Z
Asked42
58528
735554
wikitext
text/x-wiki
== Section 1: Introduction to the role and responsibilities of a reviewer ==
Welcome to Section 1. Before we dive into the technical checklists and policy specifics, we need to understand what a reviewer actually ''does''.
On Wikinews, the Reviewer right is not a trophy; it is a working role. All articles must go through independent review prior to the Publish stage (as outlined in [[WN:REV]]). Only users who have the reviewer permission can review and publish articles.
A reviewer operates with two primary responsibilities in mind: '''Quality Control''' and '''Teaching'''.
=== 1. Quality Control & Safeguarding ===
Peer review ensures no article gets published until it meets the standards of the project. Reviewers are the safety net against goofs, blunders, and serious liabilities.
* '''The Legal Shield:''' You are looking for copyright violations (copyvio), plagiarism, and libel. Publishing stolen text or defamatory claims has real-world legal consequences. Catching these is your top priority.
* '''The Reputation Shield:''' You are checking for accuracy, the [[WN:NPOV|Neutral Point of View]], and proper sourcing. If an article is heavily biased or relies on poor sources, it damages Wikinews's credibility.
* '''The Equalizer:''' Reviewers are a safety net for ''everyone''. You must never relax your reviewing standards just because an author is highly experienced. Experienced authors make mistakes too; your review protects their reputation as much as the project's.
=== 2. Teaching and Mentoring ===
You aren't just a machine that clicks "Pass" or "Fail." When an article has the `{{review}}` tag, your job is to guide the author.
According to [[Wikinews:Tips on reviewing articles]], teaching is a core function of the reviewer. When you mark an article as "Not Ready," you must point out to the author what should be done differently, what can be done better, and ''how'' to do those things.
* Leave informative comments on the article's talk page.
* Point out specific Wikinews policies (like [[WN:CITE]] or [[WN:STYLE]]).
* Even when passing an article, leaving helpful suggestions for their next piece encourages authors to grow. The future of the project depends on this mentorship.
=== What a Reviewer is NOT ===
* '''You are not the sole author:''' If an article is fundamentally broken, it is not your job to rewrite the entire piece from scratch to force it to publication. Your job is to leave feedback so the original author (or collaborators) can fix it.
* '''You are not a manual robot:''' Reviewers are strongly urged to use the '''EasyPeerReview Gadget'''. This JavaScript-based tool handles the repetitive formatting of passing/failing an article, generating talk page templates, and running style checks. (We will cover how to install and use this in Section 4).
=== Summary ===
Being a reviewer means balancing strict policy enforcement with welcoming community mentorship. You protect the wiki from bad content, and you help good contributors become great reporters.
'''[[Wikinews:Reviewing Workshop 2026/Section 2|Next ➔ Proceed to Section 2: Core review criteria]]'''
q54sq31itk5z74l3pkc2mlcdell41h6
735555
735554
2026-03-29T17:31:47Z
Asked42
58528
735555
wikitext
text/x-wiki
{{ombox
| type = notice
| image = [[File:Books-aj.svg aj ashton 01g.svg|50px]]
| text = <big>'''Welcome to the Wikinews Reviewing Workshop 2026!'''</big><br>This text-based, self-paced workshop is designed to help contributors build the skills, knowledge, and confidence needed to become active reviewers on English Wikinews.
}}
== Welcome to the Workshop ==
Hello and welcome! Reviewers are the backbone of English Wikinews. They are the final gatekeepers who ensure that every article published meets our strict standards for neutrality, verifiability, newsworthiness, and copyright compliance.
Currently, Wikinews is looking to expand its team of active reviewers. Rather than throwing you into the deep end of the reviewer nomination process, this workshop serves as a structured preparation pathway. Whether you are a seasoned reporter or a frequent copyeditor, this workshop will give you a transparent, under-the-hood look at the reviewing workflow.
=== How this workshop works ===
Because our community is global, this workshop is entirely text-based and asynchronous. You can complete it at your own pace. There are five main sections, each covering a crucial pillar of the review process.
After reading through the explanatory materials in each section, you will be given practical tasks—such as identifying errors in sample articles, answering brief quizzes, and participating in simulated reviews on the discussion pages. Experienced reviewers will be monitoring the workshop to provide feedback, answer questions, and mentor participants.
=== Workshop Modules ===
* '''Section 1:''' Introduction to the role and responsibilities of a reviewer
* '''Section 2:''' Core review criteria (newsworthiness, neutrality, verifiability, freshness, copyvio)
* '''Section 3:''' Source verification and common issues in articles
* '''Section 4:''' The review workflow (from submission to publication)
* '''Section 5:''' Post-publication review and maintenance
Participants who demonstrate a strong understanding of Wikinews policies throughout this workshop may be encouraged and mentored as official reviewer candidates at [[WN:FR/RFP]].
Ready to get started?
'''[[Wikinews:Reviewing Workshop 2026/Section 1|Click here to proceed to Section 1: Introduction to the role and responsibilities of a reviewer ➔]]'''
== Section 1: Introduction to the role and responsibilities of a reviewer ==
Welcome to Section 1. Before we dive into the technical checklists and policy specifics, we need to understand what a reviewer actually ''does''.
On Wikinews, the Reviewer right is not a trophy; it is a working role. All articles must go through independent review prior to the Publish stage (as outlined in [[WN:REV]]). Only users who have the reviewer permission can review and publish articles.
A reviewer operates with two primary responsibilities in mind: '''Quality Control''' and '''Teaching'''.
=== 1. Quality Control & Safeguarding ===
Peer review ensures no article gets published until it meets the standards of the project. Reviewers are the safety net against goofs, blunders, and serious liabilities.
* '''The Legal Shield:''' You are looking for copyright violations (copyvio), plagiarism, and libel. Publishing stolen text or defamatory claims has real-world legal consequences. Catching these is your top priority.
* '''The Reputation Shield:''' You are checking for accuracy, the [[WN:NPOV|Neutral Point of View]], and proper sourcing. If an article is heavily biased or relies on poor sources, it damages Wikinews's credibility.
* '''The Equalizer:''' Reviewers are a safety net for ''everyone''. You must never relax your reviewing standards just because an author is highly experienced. Experienced authors make mistakes too; your review protects their reputation as much as the project's.
=== 2. Teaching and Mentoring ===
You aren't just a machine that clicks "Pass" or "Fail." When an article has the `{{review}}` tag, your job is to guide the author.
According to [[Wikinews:Tips on reviewing articles]], teaching is a core function of the reviewer. When you mark an article as "Not Ready," you must point out to the author what should be done differently, what can be done better, and ''how'' to do those things.
* Leave informative comments on the article's talk page.
* Point out specific Wikinews policies (like [[WN:CITE]] or [[WN:STYLE]]).
* Even when passing an article, leaving helpful suggestions for their next piece encourages authors to grow. The future of the project depends on this mentorship.
=== What a Reviewer is NOT ===
* '''You are not the sole author:''' If an article is fundamentally broken, it is not your job to rewrite the entire piece from scratch to force it to publication. Your job is to leave feedback so the original author (or collaborators) can fix it.
* '''You are not a manual robot:''' Reviewers are strongly urged to use the '''EasyPeerReview Gadget'''. This JavaScript-based tool handles the repetitive formatting of passing/failing an article, generating talk page templates, and running style checks. (We will cover how to install and use this in Section 4).
=== Summary ===
Being a reviewer means balancing strict policy enforcement with welcoming community mentorship. You protect the wiki from bad content, and you help good contributors become great reporters.
'''[[Wikinews:Reviewing Workshop 2026/Section 2|Next ➔ Proceed to Section 2: Core review criteria]]'''
jbqmmdtarcj05t55rzyb2hrrr3fbhvj
User:Asked42/sandbox2.js
2
168408
735566
733057
2026-03-30T07:01:05Z
Asked42
58528
735566
javascript
text/javascript
(function () {
const API_URL = 'https://en.wikinews.org/w/api.php';
const WIKI_URL = 'https://en.wikinews.org/wiki/';
const INDEX_URL = 'https://en.wikinews.org/w/index.php';
const ICONS = {
info: 'https://upload.wikimedia.org/wikipedia/commons/3/3c/Codex_icon_info.svg',
edit: 'https://upload.wikimedia.org/wikipedia/commons/5/5c/Codex_icon_edit.svg',
history: 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Codex_icon_history.svg'
};
// 1. Inject CSS
const injectCSS = () => {
const style = document.createElement('style');
style.innerHTML = `
#newsroom-app { font-family: sans-serif; max-width: 1200px; margin: 0 auto; color: #202122; }
.nr-header { border: 1px solid #3366FF; border-radius: 10px; background: #fff; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.nr-header-title { background: #3366FF; color: #fff; text-align: center; font-size: 1.5em; font-weight: bold; padding: 8px; border-top-left-radius: 9px; border-top-right-radius: 9px; }
.nr-header-body { display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 15px; padding: 15px; align-items: center; }
.nr-box { background: #fff; box-shadow: 0 0 6px #808080; border-radius: 8px; padding: 15px; text-align: center; }
.nr-box a { font-weight: bold; color: #0645ad; text-decoration: none; }
.nr-create-input { padding: 6px; width: 70%; border: 1px solid #ccc; border-radius: 4px; }
.nr-btn { background: #3366FF; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; }
.nr-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 20px; }
.nr-section { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 4px; padding: 15px; }
.nr-section h3 { margin-top: 0; border-bottom: 2px solid #eaecf0; padding-bottom: 5px; color: #000; }
.nr-list { list-style-type: none; padding-left: 0; margin: 0; }
.nr-list li { padding: 8px 0; border-bottom: 1px solid #eaecf0; display: flex; justify-content: space-between; align-items: center; gap: 10px;}
.nr-list li:last-child { border-bottom: none; }
.nr-article-title { flex-grow: 1; font-weight: 500; }
.nr-meta { font-size: 0.85em; color: #54595d; display: block; }
.nr-actions { display: flex; gap: 8px; flex-shrink: 0; }
.nr-icon { width: 16px; height: 16px; opacity: 0.7; transition: opacity 0.2s; cursor: pointer; }
.nr-icon:hover { opacity: 1; }
.nr-tag { font-size: 0.75em; padding: 2px 6px; border-radius: 12px; font-weight: bold; margin-left: 5px; }
.nr-tag.urgent { background: #d33; color: white; }
.nr-tag.breaking { background: #f28b00; color: white; }
.nr-loading { font-style: italic; color: #72777d; }
.nr-empty { color: #d33; font-weight: bold; text-align: center; padding: 10px; }
`;
document.head.appendChild(style);
};
// 2. API Helper
const fetchAPI = async (params) => {
const url = new URL(API_URL);
url.search = new URLSearchParams({ ...params, format: 'json', origin: '*' });
const res = await fetch(url);
return res.json();
};
const timeAgo = (timestamp) => {
const diff = Date.now() - new Date(timestamp).getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24);
return `${days} days and ${hours} hours ago`;
};
// 3. UI Builders
const buildActions = (title) => {
const encTitle = encodeURIComponent(title);
return `
<div class="nr-actions">
<a href="${INDEX_URL}?title=${encTitle}&action=info" target="_blank" title="Info"><img src="${ICONS.info}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=edit" target="_blank" title="Edit"><img src="${ICONS.edit}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=history" target="_blank" title="History"><img src="${ICONS.history}" class="nr-icon"></a>
</div>
`;
};
const renderHeader = (container) => {
const date = new Date().toUTCString();
container.innerHTML = `
<div class="nr-header">
<div class="nr-header-title">The Newsroom — Welcome</div>
<div class="nr-header-body">
<div class="nr-box">
<form action="${INDEX_URL}" method="get" target="_blank">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="preload" value="Template:New_page">
<input type="hidden" name="editintro" value="Template:New_article_intro">
<input type="text" name="title" class="nr-create-input" placeholder="Article title...">
<button type="submit" class="nr-btn">Write an article</button>
</form>
<hr style="margin: 10px 0; border:0; border-top:1px solid #eee;">
<ul style="text-align:left; font-size: 0.9em; margin:0; padding-left: 20px;">
<li><a href="${WIKI_URL}Wikinews:RA">Request an article</a></li>
<li><a href="${WIKI_URL}Wikinews:INTERVIEW">Request an interview</a></li>
</ul>
</div>
<div style="text-align: center; line-height: 1.6;">
<em><strong>Wikinews</strong> needs <u><a href="${WIKI_URL}Template:Get_involved">your help</a></u>.</em><br>
<a href="${WIKI_URL}Wikinews:Introduction">Introduction to Wikinews</a><br>
<a href="${WIKI_URL}Wikinews:For_Wikipedians">Wikinews for Wikipedians</a><br>
<a href="${WIKI_URL}Wikinews:Policies_and_guidelines">Our policies and guidelines</a><br>
<hr>
"<em>Facts don't cease to be facts, but news ceases to be news.</em>"<br>
So get involved; improve, expand, report!<br>
Totally new? See our <a href="${WIKI_URL}Wikinews:Article_layout_in_a_nutshell"><strong>Quick Guide</strong></a>.
</div>
<div style="text-align: right; font-size: 0.85em; display:flex; flex-direction:column; justify-content:space-between;">
<div>
<em>Page last updated:</em> <br><strong>${date}</strong>
</div>
<div style="margin-top: 15px; font-size: 0.9em;">
<a href="${WIKI_URL}Wikinews:ML">Leads</a> • <a href="${WIKI_URL}Wikinews:Admin_action_alerts">Admin Alerts</a> • <a href="${WIKI_URL}Wikinews:Water_cooler">Water Cooler</a><br>
<a href="${WIKI_URL}Wikinews:Requests_for_permissions">Permissions</a> • <a href="${WIKI_URL}Wikinews:Flagged_revisions/Requests_for_permissions">Reviewer</a>
</div>
</div>
</div>
</div>
<div class="nr-grid" id="nr-sections-container"></div>
`;
};
const createSectionBox = (id, title) => {
const grid = document.getElementById('nr-sections-container');
const box = document.createElement('div');
box.className = 'nr-section';
box.innerHTML = `<h3>${title}</h3><div id="${id}"><span class="nr-loading">Loading data...</span></div>`;
grid.appendChild(box);
return document.getElementById(id);
};
// 4. Section Loaders
const loadSection1 = async () => {
const container = createSectionBox('sect-1', 'New Draft Articles (Developing)');
try {
// Get Developing Category
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Developing', cmnamespace: 0, cmlimit: 50 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return container.innerHTML = '<div class="nr-empty">No articles found for this criteria</div>';
// Check categories to filter exclusions
let catData = await fetchAPI({ action: 'query', prop: 'categories', titles: titles.join('|'), cllimit: 'max' });
let exclusions = ['Category:Published', 'Category:Prepared stories', 'Category:No publish', 'Category:Brief', 'Category:Disputed'];
let filtered = [];
for (let pageId in catData.query.pages) {
let page = catData.query.pages[pageId];
let pageCats = page.categories ? page.categories.map(c => c.title) : [];
let hasExclusion = pageCats.some(c => exclusions.includes(c));
if (!hasExclusion) filtered.push(page.title);
}
if (!filtered.length) return container.innerHTML = '<div class="nr-empty">No articles found for this criteria</div>';
// Sorting Dropdown
let html = `
<select id="s1-sort" style="margin-bottom:10px; padding:4px;">
<option value="new">Newest to Oldest</option>
<option value="old">Oldest to Newest</option>
</select>
<ul class="nr-list">
`;
filtered.forEach(t => {
html += `<li><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span>${buildActions(t)}</li>`;
});
html += '</ul>';
container.innerHTML = html;
} catch (e) { container.innerHTML = `<div class="nr-empty">Error loading data.</div>`; }
};
const loadSection2 = async () => {
const container = createSectionBox('sect-2', 'Articles Submitted for Review');
try {
// Fetch all review categories
const cats = ['Category:Review', 'Category:Breaking_review', 'Category:Urgent_review'];
let allMembers = [];
for (let cat of cats) {
let res = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 20 });
res.query.categorymembers.forEach(m => allMembers.push({ title: m.title, type: cat }));
}
if (!allMembers.length) return container.innerHTML = '<div class="nr-empty">No articles found for this criteria</div>';
// Fetch history to find submitter (Batching titles)
let titles = [...new Set(allMembers.map(m => m.title))];
let histData = await fetchAPI({ action: 'query', prop: 'revisions', titles: titles.join('|'), rvprop: 'user|timestamp|comment', rvlimit: 10 });
const submitTriggers = ["submitting article for review", "submit for review", "re submit for review", "please review this article"];
let html = '<ul class="nr-list">';
let hasResults = false;
for (let pageId in histData.query.pages) {
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
// Find matching revision
let match = revs.find(r => r.comment && submitTriggers.some(t => r.comment.toLowerCase().includes(t))) || revs[0]; // Fallback to last edit if comment missing
let catInfo = allMembers.find(m => m.title === page.title);
let tag = '';
if (catInfo.type.includes('Breaking')) tag = '<span class="nr-tag breaking">Breaking</span>';
if (catInfo.type.includes('Urgent')) tag = '<span class="nr-tag urgent">Urgent</span>';
html += `
<li>
<div>
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a> ${tag}</span>
<span class="nr-meta">Submitted by: ${match.user} | ${timeAgo(match.timestamp)}</span>
</div>
${buildActions(page.title)}
</li>
`;
hasResults = true;
}
html += '</ul>';
container.innerHTML = hasResults ? html : '<div class="nr-empty">No articles found for this criteria</div>';
} catch (e) { container.innerHTML = `<div class="nr-empty">Error loading data.</div>`; }
};
const loadSection3 = async () => {
const container = createSectionBox('sect-3', 'Marked for Re-development');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Disputed', cmnamespace: 0, cmlimit: 20 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return container.innerHTML = '<div class="nr-empty">No articles found for this criteria</div>';
let histData = await fetchAPI({ action: 'query', prop: 'revisions', titles: titles.join('|'), rvprop: 'user|timestamp|comment', rvlimit: 5 });
const disputeTrigger = "article needs improvement(s)";
let html = '<ul class="nr-list">';
for (let pageId in histData.query.pages) {
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
let match = revs.find(r => r.comment && r.comment.toLowerCase().includes(disputeTrigger)) || revs[0];
html += `
<li>
<div>
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a></span>
<span class="nr-meta">Last Peer reviewed: ${match.user}</span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
container.innerHTML = html;
} catch (e) { container.innerHTML = `<div class="nr-empty">Error loading data.</div>`; }
};
const loadStandardCategories = async (containerId, title, categories) => {
const container = createSectionBox(containerId, title);
try {
let allTitles = [];
for (let cat of categories) {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 20 });
data.query.categorymembers.forEach(m => allTitles.push(m.title));
}
allTitles = [...new Set(allTitles)];
if (!allTitles.length) return container.innerHTML = '<div class="nr-empty">No articles found for this criteria</div>';
let html = '<ul class="nr-list">';
allTitles.forEach(t => {
html += `<li><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span>${buildActions(t)}</li>`;
});
html += '</ul>';
container.innerHTML = html;
} catch (e) { container.innerHTML = `<div class="nr-empty">Error loading data.</div>`; }
};
// 5. App Initialization (Progressive Loading)
const initApp = async () => {
const appRoot = document.getElementById('newsroom-app');
if (!appRoot) {
console.error("Newsroom App: Please create a <div id='newsroom-app'></div> to hook the interface into.");
return;
}
injectCSS();
renderHeader(appRoot);
// Load sequentially to ensure UI stability and reduce immediate API spam
await loadSection1();
await loadSection2();
await loadSection3();
await loadStandardCategories('sect-4', 'Collaboration Requests', ['Category:Article assistance requested', 'Category:Wikinewsies looking for help', 'Category:Ready']);
await loadStandardCategories('sect-5', 'Abandoned or Stale', ['Category:Abandoned', 'Category:Stale']);
};
// Execute
initApp();
})();
4kknalykora471ket1tzhxqrombydz4
735568
735566
2026-03-30T07:09:35Z
Asked42
58528
735568
javascript
text/javascript
/**
* English Wikinews - Modern Newsroom Dashboard v2.0
* Target: en.wikinews.org API (Cross-origin supported)
*/
(function () {
const API_URL = 'https://en.wikinews.org/w/api.php';
const WIKI_URL = 'https://en.wikinews.org/wiki/';
const INDEX_URL = 'https://en.wikinews.org/w/index.php';
const ICONS = {
info: 'https://upload.wikimedia.org/wikipedia/commons/3/3c/Codex_icon_info.svg',
edit: 'https://upload.wikimedia.org/wikipedia/commons/5/5c/Codex_icon_edit.svg',
history: 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Codex_icon_history.svg'
};
// 1. Inject Modern CSS
const injectCSS = () => {
// Prevent injecting styles multiple times
if (document.getElementById('newsroom-styles')) return;
const style = document.createElement('style');
style.id = 'newsroom-styles';
style.innerHTML = `
#newsroom-app { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 1200px; margin: 0 auto; color: #202122; line-height: 1.5; }
/* Modern Hero Header */
.nr-hero { background: linear-gradient(135deg, #1a4b8c, #3366FF); color: #fff; border-radius: 10px; padding: 25px; margin-bottom: 25px; display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; box-shadow: 0 4px 12px rgba(51, 102, 255, 0.2); }
.nr-hero-col { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); padding: 20px; border-radius: 8px; backdrop-filter: blur(4px); }
.nr-hero h1 { margin: 0 0 15px 0; font-size: 1.8em; color: #fff; border-bottom: none; font-weight: 600; text-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.nr-hero h3 { margin: 0 0 10px 0; font-size: 1.2em; color: #fff; border-bottom: 1px solid rgba(255,255,255,0.3); padding-bottom: 5px; }
.nr-hero ul { list-style: none; padding: 0; margin: 0; }
.nr-hero li { margin-bottom: 8px; font-size: 0.95em; }
.nr-hero a { color: #e6f0ff; text-decoration: none; transition: color 0.2s; }
.nr-hero a:hover { color: #fff; text-decoration: underline; }
/* Quick Create Form */
.nr-create-form { display: flex; gap: 10px; margin-top: 15px; }
.nr-input { flex-grow: 1; padding: 10px 12px; border: none; border-radius: 6px; font-size: 1em; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); }
.nr-btn { padding: 10px 16px; background: #fff; color: #1a4b8c; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; transition: background 0.2s; }
.nr-btn:hover { background: #f0f0f0; }
/* Grid & Sections */
.nr-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 20px; }
.nr-section { background: #fff; border: 1px solid #c8ccd1; border-radius: 6px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.nr-section h3 { margin-top: 0; border-bottom: 2px solid #eaecf0; padding-bottom: 10px; color: #202122; font-size: 1.3em; }
/* Lists & Items */
.nr-list { list-style-type: none; padding-left: 0; margin: 0; }
.nr-list li { padding: 12px 0; border-bottom: 1px solid #eaecf0; display: flex; justify-content: space-between; align-items: flex-start; gap: 15px;}
.nr-list li:last-child { border-bottom: none; padding-bottom: 0; }
.nr-article-info { flex-grow: 1; }
.nr-article-title { font-weight: 600; font-size: 1.05em; display: block; margin-bottom: 4px; }
.nr-article-title a { color: #0645ad; text-decoration: none; }
.nr-article-title a:hover { text-decoration: underline; }
.nr-meta { font-size: 0.85em; color: #54595d; display: block; }
/* Actions & Tags */
.nr-actions { display: flex; gap: 8px; flex-shrink: 0; background: #f8f9fa; padding: 4px 8px; border-radius: 4px; border: 1px solid #eaecf0; }
.nr-icon { width: 18px; height: 18px; opacity: 0.6; transition: opacity 0.2s; cursor: pointer; display: block; }
.nr-icon:hover { opacity: 1; }
.nr-tag { font-size: 0.75em; padding: 2px 8px; border-radius: 12px; font-weight: bold; margin-left: 8px; vertical-align: middle; }
.nr-tag.urgent { background: #d33; color: white; }
.nr-tag.breaking { background: #ed8700; color: white; }
/* States */
.nr-loading { color: #72777d; display: flex; align-items: center; gap: 8px; font-style: italic; }
.nr-empty { color: #54595d; text-align: center; padding: 20px; background: #f8f9fa; border-radius: 4px; border: 1px dashed #c8ccd1; }
.nr-error { color: #d33; padding: 10px; background: #fee7e6; border-radius: 4px; font-size: 0.9em; }
`;
document.head.appendChild(style);
};
// 2. API Helper (with built-in error checking)
const fetchAPI = async (params) => {
const url = new URL(API_URL);
url.search = new URLSearchParams({ ...params, format: 'json', origin: '*' });
const res = await fetch(url);
const data = await res.json();
if (data.error) throw new Error(data.error.info);
return data;
};
const timeAgo = (timestamp) => {
const diff = Date.now() - new Date(timestamp).getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24);
if (days === 0 && hours === 0) return 'Just now';
return `${days}d ${hours}h ago`;
};
// 3. UI Builders
const buildActions = (title) => {
const encTitle = encodeURIComponent(title);
return `
<div class="nr-actions">
<a href="${INDEX_URL}?title=${encTitle}&action=info" target="_blank" title="Info"><img src="${ICONS.info}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=edit" target="_blank" title="Edit"><img src="${ICONS.edit}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=history" target="_blank" title="History"><img src="${ICONS.history}" class="nr-icon"></a>
</div>
`;
};
const renderHeader = (container) => {
const date = new Date().toUTCString();
container.innerHTML = `
<div class="nr-hero">
<div class="nr-hero-col" style="flex: 1.5;">
<h1>The Newsroom</h1>
<p style="margin: 0 0 15px 0; font-size: 0.95em;"><em>"Facts don't cease to be facts, but news ceases to be news."</em><br>Get involved; improve, expand, report!</p>
<form class="nr-create-form" action="${INDEX_URL}" method="get" target="_blank">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="preload" value="Template:New_page">
<input type="hidden" name="editintro" value="Template:New_article_intro">
<input type="text" name="title" class="nr-input" placeholder="Start a new article..." required>
<button type="submit" class="nr-btn">Create</button>
</form>
</div>
<div class="nr-hero-col">
<h3>Quick Links</h3>
<ul>
<li><a href="${WIKI_URL}Wikinews:RA">Request an article</a></li>
<li><a href="${WIKI_URL}Wikinews:INTERVIEW">Request an interview</a></li>
<li><a href="${WIKI_URL}Wikinews:Article_layout_in_a_nutshell">Quick Guide for Newbies</a></li>
<li><a href="${WIKI_URL}Wikinews:Policies_and_guidelines">Policies & Guidelines</a></li>
</ul>
</div>
<div class="nr-hero-col">
<h3>Administration</h3>
<ul>
<li><a href="${WIKI_URL}Wikinews:Admin_action_alerts">Admin Alerts</a> • <a href="${WIKI_URL}Wikinews:ML">Leads</a></li>
<li><a href="${WIKI_URL}Wikinews:Water_cooler">Water Cooler</a></li>
<li><a href="${WIKI_URL}Wikinews:Requests_for_permissions">Permissions</a></li>
</ul>
<div style="margin-top: 15px; font-size: 0.8em; opacity: 0.8;">
Updated: ${date}
</div>
</div>
</div>
<div class="nr-grid" id="nr-sections-container"></div>
`;
};
// 4. Section Loaders
const loadSection1 = async () => {
const el = document.getElementById('sect-1');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Developing', cmnamespace: 0, cmlimit: 50 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let catData = await fetchAPI({ action: 'query', prop: 'categories', titles: titles.join('|'), cllimit: 'max' });
let exclusions = ['Category:Published', 'Category:Prepared stories', 'Category:No publish', 'Category:Brief', 'Category:Disputed'];
let filtered = [];
for (let pageId in catData.query.pages) {
let page = catData.query.pages[pageId];
let pageCats = page.categories ? page.categories.map(c => c.title) : [];
if (!pageCats.some(c => exclusions.includes(c))) filtered.push(page.title);
}
if (!filtered.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let html = '<ul class="nr-list">';
filtered.forEach(t => {
html += `<li><div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>${buildActions(t)}</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 1 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Drafts: ${e.message}</div>`;
}
};
const loadSection2 = async () => {
const el = document.getElementById('sect-2');
try {
const cats = ['Category:Review', 'Category:Breaking_review', 'Category:Urgent_review'];
let allMembers = [];
for (let cat of cats) {
let res = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
res.query.categorymembers.forEach(m => allMembers.push({ title: m.title, type: cat }));
}
if (!allMembers.length) return el.innerHTML = '<div class="nr-empty">Queue is empty! No articles waiting for review.</div>';
// Safe slice to prevent "Too many values" API error (Max 50)
let uniqueTitles = [...new Set(allMembers.map(m => m.title))].slice(0, 45);
let histData = await fetchAPI({ action: 'query', prop: 'revisions', titles: uniqueTitles.join('|'), rvprop: 'user|timestamp|comment', rvlimit: 5 });
const triggers = ["submitting article for review", "submit for review", "re submit", "please review"];
let html = '<ul class="nr-list">';
for (let pageId in histData.query.pages) {
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
let match = revs.find(r => r.comment && triggers.some(t => r.comment.toLowerCase().includes(t))) || revs[0];
let catInfo = allMembers.find(m => m.title === page.title);
let tag = '';
if (catInfo && catInfo.type.includes('Breaking')) tag = '<span class="nr-tag breaking">Breaking</span>';
if (catInfo && catInfo.type.includes('Urgent')) tag = '<span class="nr-tag urgent">Urgent</span>';
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a> ${tag}</span>
<span class="nr-meta">Submitted by: <strong>${match.user}</strong> | ${timeAgo(match.timestamp)}</span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 2 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Review Queue: ${e.message}</div>`;
}
};
const loadSection3 = async () => {
const el = document.getElementById('sect-3');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Disputed', cmnamespace: 0, cmlimit: 20 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No disputed articles. Good job!</div>';
let safeTitles = titles.slice(0, 45); // API limit safety
let histData = await fetchAPI({ action: 'query', prop: 'revisions', titles: safeTitles.join('|'), rvprop: 'user|timestamp|comment', rvlimit: 5 });
let html = '<ul class="nr-list">';
for (let pageId in histData.query.pages) {
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
let match = revs.find(r => r.comment && r.comment.toLowerCase().includes('needs improvement')) || revs[0];
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a></span>
<span class="nr-meta">Last Peer reviewed: <strong>${match.user}</strong></span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 3 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Disputed: ${e.message}</div>`;
}
};
const loadStandardCategories = async (containerId, categories) => {
const el = document.getElementById(containerId);
try {
let allTitles = [];
for (let cat of categories) {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
data.query.categorymembers.forEach(m => allTitles.push(m.title));
}
allTitles = [...new Set(allTitles)];
if (!allTitles.length) return el.innerHTML = '<div class="nr-empty">No articles found in this category.</div>';
let html = '<ul class="nr-list">';
allTitles.forEach(t => {
html += `<li>
<div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>
${buildActions(t)}
</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error(`Standard Cat Error (${containerId}):`, e);
el.innerHTML = `<div class="nr-error">Error loading category data: ${e.message}</div>`;
}
};
// 5. App Initialization (Parallel Execution)
const initApp = () => {
const appRoot = document.getElementById('newsroom-app');
if (!appRoot) return;
// Clear root to prevent duplicates on double-run
appRoot.innerHTML = '';
injectCSS();
renderHeader(appRoot);
// Pre-create skeleton boxes
const grid = document.getElementById('nr-sections-container');
const sections = [
{ id: 'sect-1', title: 'Developing Drafts' },
{ id: 'sect-2', title: 'Submitted for Review' },
{ id: 'sect-3', title: 'Marked for Re-development' },
{ id: 'sect-4', title: 'Collaboration Requests' },
{ id: 'sect-5', title: 'Abandoned or Stale' }
];
sections.forEach(s => {
const box = document.createElement('div');
box.className = 'nr-section';
box.innerHTML = `<h3>${s.title}</h3><div id="${s.id}"><span class="nr-loading"><img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" width="16"> Loading...</span></div>`;
grid.appendChild(box);
});
// Fire API calls in parallel (they don't wait for each other!)
loadSection1();
loadSection2();
loadSection3();
loadStandardCategories('sect-4', ['Category:Article assistance requested', 'Category:Wikinewsies looking for help', 'Category:Ready']);
loadStandardCategories('sect-5', ['Category:Abandoned', 'Category:Stale']);
};
// Execute
initApp();
})();
85e5hc6qzztjdwxh1s5c0u4e50wjvde
735571
735568
2026-03-30T07:14:33Z
Asked42
58528
735571
javascript
text/javascript
/**
* English Wikinews - Modern Newsroom Dashboard v2.1
* Target: en.wikinews.org API (Cross-origin supported)
* Fix: Replaced bulk rvlimit queries with concurrent individual queries.
*/
(function () {
const API_URL = 'https://en.wikinews.org/w/api.php';
const WIKI_URL = 'https://en.wikinews.org/wiki/';
const INDEX_URL = 'https://en.wikinews.org/w/index.php';
const ICONS = {
info: 'https://upload.wikimedia.org/wikipedia/commons/3/3c/Codex_icon_info.svg',
edit: 'https://upload.wikimedia.org/wikipedia/commons/5/5c/Codex_icon_edit.svg',
history: 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Codex_icon_history.svg'
};
// 1. Inject Modern CSS (Header CSS removed)
const injectCSS = () => {
if (document.getElementById('newsroom-styles')) return;
const style = document.createElement('style');
style.id = 'newsroom-styles';
style.innerHTML = `
#newsroom-app { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 1200px; margin: 0 auto; color: #202122; line-height: 1.5; }
/* Grid & Sections */
.nr-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 20px; }
.nr-section { background: #fff; border: 1px solid #c8ccd1; border-radius: 6px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.nr-section h3 { margin-top: 0; border-bottom: 2px solid #eaecf0; padding-bottom: 10px; color: #202122; font-size: 1.3em; }
/* Lists & Items */
.nr-list { list-style-type: none; padding-left: 0; margin: 0; }
.nr-list li { padding: 12px 0; border-bottom: 1px solid #eaecf0; display: flex; justify-content: space-between; align-items: flex-start; gap: 15px;}
.nr-list li:last-child { border-bottom: none; padding-bottom: 0; }
.nr-article-info { flex-grow: 1; }
.nr-article-title { font-weight: 600; font-size: 1.05em; display: block; margin-bottom: 4px; }
.nr-article-title a { color: #0645ad; text-decoration: none; }
.nr-article-title a:hover { text-decoration: underline; }
.nr-meta { font-size: 0.85em; color: #54595d; display: block; }
/* Actions & Tags */
.nr-actions { display: flex; gap: 8px; flex-shrink: 0; background: #f8f9fa; padding: 4px 8px; border-radius: 4px; border: 1px solid #eaecf0; }
.nr-icon { width: 18px; height: 18px; opacity: 0.6; transition: opacity 0.2s; cursor: pointer; display: block; }
.nr-icon:hover { opacity: 1; }
.nr-tag { font-size: 0.75em; padding: 2px 8px; border-radius: 12px; font-weight: bold; margin-left: 8px; vertical-align: middle; }
.nr-tag.urgent { background: #d33; color: white; }
.nr-tag.breaking { background: #ed8700; color: white; }
/* States */
.nr-loading { color: #72777d; display: flex; align-items: center; gap: 8px; font-style: italic; }
.nr-empty { color: #54595d; text-align: center; padding: 20px; background: #f8f9fa; border-radius: 4px; border: 1px dashed #c8ccd1; }
.nr-error { color: #d33; padding: 10px; background: #fee7e6; border-radius: 4px; font-size: 0.9em; margin-bottom: 10px; }
`;
document.head.appendChild(style);
};
// 2. API Helper
const fetchAPI = async (params) => {
const url = new URL(API_URL);
url.search = new URLSearchParams({ ...params, format: 'json', origin: '*' });
const res = await fetch(url);
const data = await res.json();
if (data.error) throw new Error(data.error.info);
return data;
};
const timeAgo = (timestamp) => {
if (!timestamp) return 'Unknown time';
const diff = Date.now() - new Date(timestamp).getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24);
if (days === 0 && hours === 0) return 'Just now';
return `${days}d ${hours}h ago`;
};
// 3. UI Builders
const buildActions = (title) => {
const encTitle = encodeURIComponent(title);
return `
<div class="nr-actions">
<a href="${INDEX_URL}?title=${encTitle}&action=info" target="_blank" title="Info"><img src="${ICONS.info}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=edit" target="_blank" title="Edit"><img src="${ICONS.edit}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=history" target="_blank" title="History"><img src="${ICONS.history}" class="nr-icon"></a>
</div>
`;
};
// 4. Section Loaders
const loadSection1 = async () => {
const el = document.getElementById('sect-1');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Developing', cmnamespace: 0, cmlimit: 50 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let catData = await fetchAPI({ action: 'query', prop: 'categories', titles: titles.join('|'), cllimit: 'max' });
let exclusions = ['Category:Published', 'Category:Prepared stories', 'Category:No publish', 'Category:Brief', 'Category:Disputed'];
let filtered = [];
for (let pageId in catData.query.pages) {
let page = catData.query.pages[pageId];
let pageCats = page.categories ? page.categories.map(c => c.title) : [];
if (!pageCats.some(c => exclusions.includes(c))) filtered.push(page.title);
}
if (!filtered.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let html = '<ul class="nr-list">';
filtered.forEach(t => {
html += `<li><div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>${buildActions(t)}</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 1 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Drafts: ${e.message}</div>`;
}
};
const loadSection2 = async () => {
const el = document.getElementById('sect-2');
try {
const cats = ['Category:Review', 'Category:Breaking_review', 'Category:Urgent_review'];
let allMembers = [];
for (let cat of cats) {
let res = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
res.query.categorymembers.forEach(m => allMembers.push({ title: m.title, type: cat }));
}
if (!allMembers.length) return el.innerHTML = '<div class="nr-empty">Queue is empty! No articles waiting for review.</div>';
let uniqueTitles = [...new Set(allMembers.map(m => m.title))].slice(0, 45);
// FIX: Fetch history for each title individually to allow rvlimit=5
const historyPromises = uniqueTitles.map(async (title) => {
try {
return await fetchAPI({ action: 'query', prop: 'revisions', titles: title, rvprop: 'user|timestamp|comment', rvlimit: 5 });
} catch (e) { return null; }
});
const historyResults = await Promise.all(historyPromises);
const triggers = ["submitting article for review", "submit for review", "re submit", "please review"];
let html = '<ul class="nr-list">';
for (let histData of historyResults) {
if (!histData || !histData.query || !histData.query.pages) continue;
let pageId = Object.keys(histData.query.pages)[0];
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
// Fallback to the latest edit if we don't find the exact comment
let match = revs.find(r => r.comment && triggers.some(t => r.comment.toLowerCase().includes(t))) || revs[0] || { user: 'Unknown', timestamp: null };
let catInfo = allMembers.find(m => m.title === page.title);
let tag = '';
if (catInfo && catInfo.type.includes('Breaking')) tag = '<span class="nr-tag breaking">Breaking</span>';
if (catInfo && catInfo.type.includes('Urgent')) tag = '<span class="nr-tag urgent">Urgent</span>';
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a> ${tag}</span>
<span class="nr-meta">Submitted by: <strong>${match.user}</strong> | ${timeAgo(match.timestamp)}</span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 2 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Review Queue: ${e.message}</div>`;
}
};
const loadSection3 = async () => {
const el = document.getElementById('sect-3');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Disputed', cmnamespace: 0, cmlimit: 20 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No disputed articles. Good job!</div>';
let safeTitles = titles.slice(0, 45);
// FIX: Fetch history for each title individually to allow rvlimit=5
const historyPromises = safeTitles.map(async (title) => {
try {
return await fetchAPI({ action: 'query', prop: 'revisions', titles: title, rvprop: 'user|timestamp|comment', rvlimit: 5 });
} catch (e) { return null; }
});
const historyResults = await Promise.all(historyPromises);
let html = '<ul class="nr-list">';
for (let histData of historyResults) {
if (!histData || !histData.query || !histData.query.pages) continue;
let pageId = Object.keys(histData.query.pages)[0];
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
let match = revs.find(r => r.comment && r.comment.toLowerCase().includes('needs improvement')) || revs[0] || { user: 'Unknown' };
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a></span>
<span class="nr-meta">Last Peer reviewed: <strong>${match.user}</strong></span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 3 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Disputed: ${e.message}</div>`;
}
};
const loadStandardCategories = async (containerId, categories) => {
const el = document.getElementById(containerId);
try {
let allTitles = [];
for (let cat of categories) {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
data.query.categorymembers.forEach(m => allTitles.push(m.title));
}
allTitles = [...new padding(allTitles)];
if (!allTitles.length) return el.innerHTML = '<div class="nr-empty">No articles found in this category.</div>';
let html = '<ul class="nr-list">';
allTitles.forEach(t => {
html += `<li>
<div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>
${buildActions(t)}
</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error(`Standard Cat Error (${containerId}):`, e);
el.innerHTML = `<div class="nr-error">Error loading category data: ${e.message}</div>`;
}
};
// 5. App Initialization
const initApp = () => {
const appRoot = document.getElementById('newsroom-app');
if (!appRoot) return;
// Clear root to prevent duplicates
appRoot.innerHTML = '';
injectCSS();
// Create Grid Container
const gridContainer = document.createElement('div');
gridContainer.className = 'nr-grid';
gridContainer.id = 'nr-sections-container';
appRoot.appendChild(gridContainer);
const sections = [
{ id: 'sect-1', title: 'Developing Drafts' },
{ id: 'sect-2', title: 'Submitted for Review' },
{ id: 'sect-3', title: 'Marked for Re-development' },
{ id: 'sect-4', title: 'Collaboration Requests' },
{ id: 'sect-5', title: 'Abandoned or Stale' }
];
sections.forEach(s => {
const box = document.createElement('div');
box.className = 'nr-section';
box.innerHTML = `<h3>${s.title}</h3><div id="${s.id}"><span class="nr-loading"><img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" width="16"> Loading...</span></div>`;
gridContainer.appendChild(box);
});
// Fire API calls in parallel
loadSection1();
loadSection2();
loadSection3();
loadStandardCategories('sect-4', ['Category:Article assistance requested', 'Category:Wikinewsies looking for help', 'Category:Ready']);
loadStandardCategories('sect-5', ['Category:Abandoned', 'Category:Stale']);
};
// Execute
initApp();
})();
40pmwc6xmz6vu6eh9h4xomza9hpyd5l
735572
735571
2026-03-30T07:23:34Z
Asked42
58528
735572
javascript
text/javascript
(function () {
const API_URL = 'https://en.wikinews.org/w/api.php';
const WIKI_URL = 'https://en.wikinews.org/wiki/';
const INDEX_URL = 'https://en.wikinews.org/w/index.php';
const ICONS = {
info: 'https://upload.wikimedia.org/wikipedia/commons/3/3c/Codex_icon_info.svg',
edit: 'https://upload.wikimedia.org/wikipedia/commons/5/5c/Codex_icon_edit.svg',
history: 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Codex_icon_history.svg'
};
// 1. Inject Modern CSS
const injectCSS = () => {
if (document.getElementById('newsroom-styles')) return;
const style = document.createElement('style');
style.id = 'newsroom-styles';
style.innerHTML = `
#newsroom-app { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 1200px; margin: 0 auto; color: #202122; line-height: 1.5; }
/* Grid & Sections */
.nr-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 20px; }
.nr-section { background: #fff; border: 1px solid #c8ccd1; border-radius: 6px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.nr-section h3 { margin-top: 0; border-bottom: 2px solid #eaecf0; padding-bottom: 10px; color: #202122; font-size: 1.3em; }
/* Lists & Items */
.nr-list { list-style-type: none; padding-left: 0; margin: 0; }
.nr-list li { padding: 12px 0; border-bottom: 1px solid #eaecf0; display: flex; justify-content: space-between; align-items: flex-start; gap: 15px;}
.nr-list li:last-child { border-bottom: none; padding-bottom: 0; }
.nr-article-info { flex-grow: 1; }
.nr-article-title { font-weight: 600; font-size: 1.05em; display: block; margin-bottom: 4px; }
.nr-article-title a { color: #0645ad; text-decoration: none; }
.nr-article-title a:hover { text-decoration: underline; }
.nr-meta { font-size: 0.85em; color: #54595d; display: block; }
/* Actions & Tags */
.nr-actions { display: flex; gap: 8px; flex-shrink: 0; background: #f8f9fa; padding: 4px 8px; border-radius: 4px; border: 1px solid #eaecf0; }
.nr-icon { width: 18px; height: 18px; opacity: 0.6; transition: opacity 0.2s; cursor: pointer; display: block; }
.nr-icon:hover { opacity: 1; }
.nr-tag { font-size: 0.75em; padding: 2px 8px; border-radius: 12px; font-weight: bold; margin-left: 8px; vertical-align: middle; }
.nr-tag.urgent { background: #d33; color: white; }
.nr-tag.breaking { background: #ed8700; color: white; }
/* States */
.nr-loading { color: #72777d; display: flex; align-items: center; gap: 8px; font-style: italic; }
.nr-empty { color: #54595d; text-align: center; padding: 20px; background: #f8f9fa; border-radius: 4px; border: 1px dashed #c8ccd1; }
.nr-error { color: #d33; padding: 10px; background: #fee7e6; border-radius: 4px; font-size: 0.9em; margin-bottom: 10px; }
`;
document.head.appendChild(style);
};
// 2. API Helper
const fetchAPI = async (params) => {
const url = new URL(API_URL);
url.search = new URLSearchParams({ ...params, format: 'json', origin: '*' });
const res = await fetch(url);
const data = await res.json();
if (data.error) throw new Error(data.error.info);
return data;
};
const timeAgo = (timestamp) => {
if (!timestamp) return 'Unknown time';
const diff = Date.now() - new Date(timestamp).getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24);
if (days === 0 && hours === 0) return 'Just now';
return `${days}d ${hours}h ago`;
};
// 3. UI Builders
const buildActions = (title) => {
const encTitle = encodeURIComponent(title);
return `
<div class="nr-actions">
<a href="${INDEX_URL}?title=${encTitle}&action=info" target="_blank" title="Info"><img src="${ICONS.info}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=edit" target="_blank" title="Edit"><img src="${ICONS.edit}" class="nr-icon"></a>
<a href="${INDEX_URL}?title=${encTitle}&action=history" target="_blank" title="History"><img src="${ICONS.history}" class="nr-icon"></a>
</div>
`;
};
// 4. Section Loaders
const loadSection1 = async () => {
const el = document.getElementById('sect-1');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Developing', cmnamespace: 0, cmlimit: 50 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let catData = await fetchAPI({ action: 'query', prop: 'categories', titles: titles.join('|'), cllimit: 'max' });
let exclusions = ['Category:Published', 'Category:Prepared stories', 'Category:No publish', 'Category:Brief', 'Category:Disputed'];
let filtered = [];
for (let pageId in catData.query.pages) {
let page = catData.query.pages[pageId];
let pageCats = page.categories ? page.categories.map(c => c.title) : [];
if (!pageCats.some(c => exclusions.includes(c))) filtered.push(page.title);
}
if (!filtered.length) return el.innerHTML = '<div class="nr-empty">No developing articles right now.</div>';
let html = '<ul class="nr-list">';
filtered.forEach(t => {
html += `<li><div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>${buildActions(t)}</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 1 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Drafts: ${e.message}</div>`;
}
};
const loadSection2 = async () => {
const el = document.getElementById('sect-2');
try {
const cats = ['Category:Review', 'Category:Breaking_review', 'Category:Urgent_review'];
let allMembers = [];
for (let cat of cats) {
let res = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
res.query.categorymembers.forEach(m => allMembers.push({ title: m.title, type: cat }));
}
if (!allMembers.length) return el.innerHTML = '<div class="nr-empty">Queue is empty! No articles waiting for review.</div>';
let uniqueTitles = [...new Set(allMembers.map(m => m.title))].slice(0, 45);
const historyPromises = uniqueTitles.map(async (title) => {
try {
// Fetch up to 10 recent revisions just to give us a better chance of catching the edit summary
return await fetchAPI({ action: 'query', prop: 'revisions', titles: title, rvprop: 'user|timestamp|comment', rvlimit: 10 });
} catch (e) { return null; }
});
const historyResults = await Promise.all(historyPromises);
const triggers = ["submitting article for review", "submit for review", "re submit", "please review"];
let html = '<ul class="nr-list">';
for (let histData of historyResults) {
if (!histData || !histData.query || !histData.query.pages) continue;
let pageId = Object.keys(histData.query.pages)[0];
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
// Strictly find the edit summary. No fallback to the latest edit.
let match = revs.find(r => r.comment && triggers.some(t => r.comment.toLowerCase().includes(t)));
let submitterDisplay = match ? match.user : 'Not found';
let timeDisplay = match ? timeAgo(match.timestamp) : 'Unknown time';
let catInfo = allMembers.find(m => m.title === page.title);
let tag = '';
if (catInfo && catInfo.type.includes('Breaking')) tag = '<span class="nr-tag breaking">Breaking</span>';
if (catInfo && catInfo.type.includes('Urgent')) tag = '<span class="nr-tag urgent">Urgent</span>';
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a> ${tag}</span>
<span class="nr-meta">Submitted by: <strong>${submitterDisplay}</strong> | ${timeDisplay}</span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 2 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Review Queue: ${e.message}</div>`;
}
};
const loadSection3 = async () => {
const el = document.getElementById('sect-3');
try {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Disputed', cmnamespace: 0, cmlimit: 20 });
let titles = data.query.categorymembers.map(m => m.title);
if (!titles.length) return el.innerHTML = '<div class="nr-empty">No disputed articles. Good job!</div>';
let safeTitles = titles.slice(0, 45);
const historyPromises = safeTitles.map(async (title) => {
try {
return await fetchAPI({ action: 'query', prop: 'revisions', titles: title, rvprop: 'user|timestamp|comment', rvlimit: 10 });
} catch (e) { return null; }
});
const historyResults = await Promise.all(historyPromises);
let html = '<ul class="nr-list">';
for (let histData of historyResults) {
if (!histData || !histData.query || !histData.query.pages) continue;
let pageId = Object.keys(histData.query.pages)[0];
let page = histData.query.pages[pageId];
let revs = page.revisions || [];
// Strictly find the edit summary. No fallback to the latest edit.
let match = revs.find(r => r.comment && r.comment.toLowerCase().includes('needs improvement'));
let reviewerDisplay = match ? match.user : 'Not found';
html += `
<li>
<div class="nr-article-info">
<span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(page.title)}">${page.title}</a></span>
<span class="nr-meta">Last Peer reviewed: <strong>${reviewerDisplay}</strong></span>
</div>
${buildActions(page.title)}
</li>
`;
}
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error("Section 3 Error:", e);
el.innerHTML = `<div class="nr-error">Error loading Disputed: ${e.message}</div>`;
}
};
const loadStandardCategories = async (containerId, categories) => {
const el = document.getElementById(containerId);
try {
let allTitles = [];
for (let cat of categories) {
let data = await fetchAPI({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 15 });
data.query.categorymembers.forEach(m => allTitles.push(m.title));
}
// Fixed typo here: replaced 'padding' with 'Set'
allTitles = [...new Set(allTitles)];
if (!allTitles.length) return el.innerHTML = '<div class="nr-empty">No articles found in this category.</div>';
let html = '<ul class="nr-list">';
allTitles.forEach(t => {
html += `<li>
<div class="nr-article-info"><span class="nr-article-title"><a href="${WIKI_URL}${encodeURIComponent(t)}">${t}</a></span></div>
${buildActions(t)}
</li>`;
});
html += '</ul>';
el.innerHTML = html;
} catch (e) {
console.error(`Standard Cat Error (${containerId}):`, e);
el.innerHTML = `<div class="nr-error">Error loading category data: ${e.message}</div>`;
}
};
// 5. App Initialization
const initApp = () => {
const appRoot = document.getElementById('newsroom-app');
if (!appRoot) return;
// Clear root to prevent duplicates
appRoot.innerHTML = '';
injectCSS();
// Create Grid Container
const gridContainer = document.createElement('div');
gridContainer.className = 'nr-grid';
gridContainer.id = 'nr-sections-container';
appRoot.appendChild(gridContainer);
const sections = [
{ id: 'sect-1', title: 'Developing Drafts' },
{ id: 'sect-2', title: 'Submitted for Review' },
{ id: 'sect-3', title: 'Marked for Re-development' },
{ id: 'sect-4', title: 'Collaboration Requests' },
{ id: 'sect-5', title: 'Abandoned or Stale' }
];
sections.forEach(s => {
const box = document.createElement('div');
box.className = 'nr-section';
box.innerHTML = `<h3>${s.title}</h3><div id="${s.id}"><span class="nr-loading"><img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" width="16"> Loading...</span></div>`;
gridContainer.appendChild(box);
});
// Fire API calls in parallel
loadSection1();
loadSection2();
loadSection3();
loadStandardCategories('sect-4', ['Category:Article assistance requested', 'Category:Wikinewsies looking for help', 'Category:Ready']);
loadStandardCategories('sect-5', ['Category:Abandoned', 'Category:Stale']);
};
// Execute
initApp();
})();
ddx2rnpycyq6nudxq8aohm8iunr39rd
File:Trumpeter Swan at Akron Zoo 2.jpg
6
174480
735562
735249
2026-03-29T21:44:07Z
Kevin Payravi
37563
Kevin Payravi uploaded a new version of [[File:Trumpeter Swan at Akron Zoo 2.jpg]]
735249
wikitext
text/x-wiki
=={{int:filedesc}}==
{{Information
|description={{en|1=Testing swan}}
|date=2009-09-07
|source={{own}}
|author=[[User:Kevin Payravi]]
|permission=
|other versions=
}}
=={{int:license-header}}==
{{self|cc-by-sa-4.0}}
4esx2k3934hxmd4j57pgy1o0d37fg82
Welcome To SEDTI
0
174549
735556
2026-03-29T17:36:41Z
Zmashregh
73330
It is a mockup for myself
735556
wikitext
text/x-wiki
== '''Welcome''' ==
Welcome to the School of Engineering Design and Teaching Innovation (SEDTI), and congratulations on joining us as a professor.
We are pleased to have you join a community dedicated to shaping the future of engineering education.
The School of Engineering Design and Teaching Innovation (SEDTI) is redefining the engineering learning experience at the Faculty of Engineering. With an approach adapted to the realities of the 21st century, our mission is to train the next generation of changemakers.
This wiki has been created as a practical guide to help you get started with the semester. It will introduce you to key information that can support your onboarding, and teaching process. Also, help you navigate the academic environment, and connect you with the services and people available to assist you along the way.
Whether you are preparing your courses, exploring teaching technologies, or looking for points of contact within the Faculty, this guide is designed to support you from the beginning of your journey with us.
Welcome to SEDTI — we look forward to supporting you and working together to create meaningful learning experiences for our students.
pf5j3tl6wvgps0dlbrknubgfxdemxji
735558
735556
2026-03-29T17:38:08Z
Zmashregh
73330
735558
wikitext
text/x-wiki
[[File:University of Ottawa Logo.svg.png|thumb]]
== '''Welcome''' ==
Welcome to the School of Engineering Design and Teaching Innovation (SEDTI), and congratulations on joining us as a professor.
We are pleased to have you join a community dedicated to shaping the future of engineering education.
The School of Engineering Design and Teaching Innovation (SEDTI) is redefining the engineering learning experience at the Faculty of Engineering. With an approach adapted to the realities of the 21st century, our mission is to train the next generation of changemakers.
This wiki has been created as a practical guide to help you get started with the semester. It will introduce you to key information that can support your onboarding, and teaching process. Also, help you navigate the academic environment, and connect you with the services and people available to assist you along the way.
Whether you are preparing your courses, exploring teaching technologies, or looking for points of contact within the Faculty, this guide is designed to support you from the beginning of your journey with us.
Welcome to SEDTI — we look forward to supporting you and working together to create meaningful learning experiences for our students.
76nwrij45qvjsup8cjwg77osi96l1ew
735559
735558
2026-03-29T17:39:28Z
Zmashregh
73330
735559
wikitext
text/x-wiki
[[File:University_of_Ottawa_Logo.svg.png|right|frameless]]
== '''Welcome''' ==
Welcome to the School of Engineering Design and Teaching Innovation (SEDTI), and congratulations on joining us as a professor.
We are pleased to have you join a community dedicated to shaping the future of engineering education.
The School of Engineering Design and Teaching Innovation (SEDTI) is redefining the engineering learning experience at the Faculty of Engineering. With an approach adapted to the realities of the 21st century, our mission is to train the next generation of changemakers.
This wiki has been created as a practical guide to help you get started with the semester. It will introduce you to key information that can support your onboarding, and teaching process. Also, help you navigate the academic environment, and connect you with the services and people available to assist you along the way.
Whether you are preparing your courses, exploring teaching technologies, or looking for points of contact within the Faculty, this guide is designed to support you from the beginning of your journey with us.
Welcome to SEDTI — we look forward to supporting you and working together to create meaningful learning experiences for our students.
g6mmi7dg0s4wxxuf9qddu9vdp431uvv
735560
735559
2026-03-29T17:40:00Z
Zmashregh
73330
735560
wikitext
text/x-wiki
[[File:University_of_Ottawa_Logo.svg.png|right|frameless|159x159px]]
== '''Welcome''' ==
Welcome to the School of Engineering Design and Teaching Innovation (SEDTI), and congratulations on joining us as a professor.
We are pleased to have you join a community dedicated to shaping the future of engineering education.
The School of Engineering Design and Teaching Innovation (SEDTI) is redefining the engineering learning experience at the Faculty of Engineering. With an approach adapted to the realities of the 21st century, our mission is to train the next generation of changemakers.
This wiki has been created as a practical guide to help you get started with the semester. It will introduce you to key information that can support your onboarding, and teaching process. Also, help you navigate the academic environment, and connect you with the services and people available to assist you along the way.
Whether you are preparing your courses, exploring teaching technologies, or looking for points of contact within the Faculty, this guide is designed to support you from the beginning of your journey with us.
Welcome to SEDTI — we look forward to supporting you and working together to create meaningful learning experiences for our students.
qk2eca4fke8pgalqwsl1n0uc4lmt1bd
735561
735560
2026-03-29T21:11:07Z
Zmashregh
73330
735561
wikitext
text/x-wiki
[[File:University_of_Ottawa_Logo.svg.png|right|frameless|159x159px]]
== '''Welcome''' ==
Welcome to the School of Engineering Design and Teaching Innovation (SEDTI), and congratulations on joining us as a professor.
We are pleased to have you join a community dedicated to shaping the future of engineering education.
The School of Engineering Design and Teaching Innovation (SEDTI) is redefining the engineering learning experience at the Faculty of Engineering. With an approach adapted to the realities of the 21st century, our mission is to train the next generation of changemakers.
This wiki has been created as a practical guide to help you get started with the semester. It will introduce you to key information that can support your onboarding, and teaching process. Also, help you navigate the academic environment, and connect you with the services and people available to assist you along the way.
Whether you are preparing your courses, exploring teaching technologies, or looking for points of contact within the Faculty, this guide is designed to support you from the beginning of your journey with us.
Welcome to SEDTI — we look forward to supporting you and working together to create meaningful learning experiences for our students.
== About the school of Engineering & Design in the Uottawa (SEDTI) ==
The School of Engineering Design and Teaching Innovation brings engineering design, multidisciplinary education, experiential learning and professional skills development to both our undergraduate and graduate students in all programs at the faculty.
You can reach The School of Engineering And Design here: [https://www.uottawa.ca/faculty-engineering/school-engineering-design-teaching-innovation Click]
== Undergraduate Programs ==
'''[https://www.uottawa.ca/faculty-engineering/undergraduate-studies/programs/multidisciplinary-design Multidisciplinary Design]'''
== Graduate Programs ==
'''[https://www.uottawa.ca/faculty-engineering/graduate-studies/programs/digital-transformation-innovation Digital Transformation and Innovation]'''
'''[https://www.uottawa.ca/faculty-engineering/graduate-studies/programs/engineering-design Entrepreneurial Engineering Design]'''
'''[https://www.uottawa.ca/faculty-engineering/graduate-studies/programs/engineering-management Engineering Management]'''
'''[https://www.uottawa.ca/faculty-engineering/graduate-studies/programs/systems-science System Science]'''
== Online Programs ==
[https://www.uottawa.ca/faculty-engineering/online-programs/master-engineering-management '''Master of Engineering Management (online)''']
'''[https://www.uottawa.ca/faculty-engineering/online-programs/master-engineering-management Graduate Diploma in Engineering Management (online)]'''
'''Master of Digital Transformation and Innovation (online)'''
'''Master of Interdisciplinary Artificial Intelligence (online)'''
879x7fmwsacfbvn4c44ew57y7d8w8xs
File:University of Ottawa Logo.svg.png
6
174550
735557
2026-03-29T17:37:13Z
Zmashregh
73330
735557
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
DeleteMeBDP new
0
174551
735574
2026-03-30T09:51:33Z
DWalden (WMF)
44719
Created page with "new page"
735574
wikitext
text/x-wiki
new page
bmqnsarccscewgtj64w4iu15uch3489
Bug 33151 new page
0
174552
735575
2026-03-30T09:53:04Z
~2026-18850-85
73288
Created page with "this is a new page"
735575
wikitext
text/x-wiki
this is a new page
k3k2n6yaic8gwi9dv3xjmg5s2ji37m7
Event:Bug 33151 new page
1728
174553
735576
2026-03-30T09:53:35Z
~2026-18850-85
73288
Created page with "this is a new event"
735576
wikitext
text/x-wiki
this is a new event
3o08fjdyqi3xm5l8ahkg6s1xa9ocbtt
Bug 33151 new page1
0
174554
735577
2026-03-30T09:54:22Z
~2026-18850-85
73288
Created page with "this is a new page"
735577
wikitext
text/x-wiki
this is a new page
k3k2n6yaic8gwi9dv3xjmg5s2ji37m7
Event:Bug 33151 new page1
1728
174555
735578
2026-03-30T09:55:04Z
~2026-18850-85
73288
Created page with "this is a new event"
735578
wikitext
text/x-wiki
this is a new event
3o08fjdyqi3xm5l8ahkg6s1xa9ocbtt
444 new page
0
174556
735582
2026-03-30T10:19:44Z
~2026-18850-85
73288
Created page with "new page"
735582
wikitext
text/x-wiki
new page
bmqnsarccscewgtj64w4iu15uch3489
735583
735582
2026-03-30T10:20:10Z
~2026-18850-85
73288
735583
wikitext
text/x-wiki
new page
this is an edit
npmd5av30u20mgfajvmn2gs94w3ibvq