Wikipedia
testwiki
https://test.wikipedia.org/wiki/Main_Page
MediaWiki 1.46.0-wmf.23
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:Requests/Permissions
4
32559
737178
737152
2026-04-08T13:20:04Z
Barras
6527
/* Requests for user rights */ -1 done
737178
wikitext
text/x-wiki
<noinclude>{{Shortcut|WP:R/P|WP:RfP|WP:RfA|WP:PERM|WP:RFR|WP:RFPERM}}</noinclude>
{{Wikipedia:Requests/Top}}
== Requests for user rights ==
* Subpages: [[Wikipedia:Requests/Permissions/All|All (current and archived)]]
* Request:
<inputbox>
type=create
prefix=Wikipedia:Requests/Permissions/
preload=Template:PA2
buttonlabel=Requests for user rights
placeholder=Enter your username
</inputbox>
After creating the subpage, come back here and transclude the page below (<code><nowiki>{{Wikipedia:Requests/Permissions/Example}}</nowiki></code>).
<!-- Please transclude your requests below this line, LATEST AT THE TOP, in the form {{Wikipedia:Requests/Permissions/USERNAME}} -->
{{Wikipedia:Requests/Permissions/Plantaest}}
{{wikipedia:Requests/Permissions/PieAlt}}
<!-- NEW ENTRIES AT THE TOP, NOT HERE -->
pd87w26dbfeb2f6tfmdc4v94na6qqwd
737180
737178
2026-04-08T13:21:25Z
Barras
6527
/* Requests for user rights */ -1 done
737180
wikitext
text/x-wiki
<noinclude>{{Shortcut|WP:R/P|WP:RfP|WP:RfA|WP:PERM|WP:RFR|WP:RFPERM}}</noinclude>
{{Wikipedia:Requests/Top}}
== Requests for user rights ==
* Subpages: [[Wikipedia:Requests/Permissions/All|All (current and archived)]]
* Request:
<inputbox>
type=create
prefix=Wikipedia:Requests/Permissions/
preload=Template:PA2
buttonlabel=Requests for user rights
placeholder=Enter your username
</inputbox>
After creating the subpage, come back here and transclude the page below (<code><nowiki>{{Wikipedia:Requests/Permissions/Example}}</nowiki></code>).
<!-- Please transclude your requests below this line, LATEST AT THE TOP, in the form {{Wikipedia:Requests/Permissions/USERNAME}} -->
{{wikipedia:Requests/Permissions/PieAlt}}
<!-- NEW ENTRIES AT THE TOP, NOT HERE -->
dh82oumajd7c6kdwucjwkz82g7fuyql
737328
737180
2026-04-09T11:38:00Z
Barras
6527
/* Requests for user rights */ none atm
737328
wikitext
text/x-wiki
<noinclude>{{Shortcut|WP:R/P|WP:RfP|WP:RfA|WP:PERM|WP:RFR|WP:RFPERM}}</noinclude>
{{Wikipedia:Requests/Top}}
== Requests for user rights ==
* Subpages: [[Wikipedia:Requests/Permissions/All|All (current and archived)]]
* Request:
<inputbox>
type=create
prefix=Wikipedia:Requests/Permissions/
preload=Template:PA2
buttonlabel=Requests for user rights
placeholder=Enter your username
</inputbox>
After creating the subpage, come back here and transclude the page below (<code><nowiki>{{Wikipedia:Requests/Permissions/Example}}</nowiki></code>).
<!-- Please transclude your requests below this line, LATEST AT THE TOP, in the form {{Wikipedia:Requests/Permissions/USERNAME}} -->
<!-- NEW ENTRIES AT THE TOP, NOT HERE -->
4to3ioxi4gykpf1uc4989asi41mhvsu
Page810
0
49734
737212
373202
2026-04-08T17:44:09Z
Reallylongstringksdjuhaifuhwefwjshfsefkhbfkbwhefkbwhebdwkshbdsjfh
72932
737212
wikitext
text/x-wiki
'''Page810'''
This in test page No. 810
test edit!
[[Category:Category with multiple files]]
egsq4cl2z84v2fgd770bwmzq2msj1ml
Qhapaq p'anqa
0
49779
737229
666939
2026-04-08T18:14:44Z
Bad-editor-111
62134
AutoModerator Test
737229
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
Invisible unicode
0
58556
737226
689348
2026-04-08T18:13:38Z
Bad-editor-111
62134
Replaced content with "#"
737226
wikitext
text/x-wiki
#
od1fgppf1qdjytpd33ma0xrincefby9
Multipage poem test
0
74032
737230
446018
2026-04-08T18:15:07Z
Bad-editor-111
62134
Blanked the page
737230
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
737231
737230
2026-04-08T18:15:08Z
AutoModeratorTest
61468
Reverted edit by [[Special:Contributions/Bad-editor-111|Bad-editor-111]] ([[User talk:Bad-editor-111|talk]]) to last revision by [[User:つがる|つがる]]
183800
wikitext
text/x-wiki
This page is to test the interaction between [[mw:Extension:Poem|Extension:Poem]] and <noinclude>. This problem has been reported at [[bugzilla:55825]].
== Poem test description ==
The "poem" below should ideally be continuous, with no break ''visible'' or in ''generated HTML''. The test result should thus be identical to the expected result below both visibly and in generated HTML. (Transcluded test pages: [[Multipage poem test/a|part a]], [[Multipage poem test/b|part b]], [[Multipage poem test/c|part c]].)
=== Test ===
{{:Multipage poem test/a}}{{:Multipage poem test/b}}{{:Multipage poem test/c}}
=== Expected result ===
<poem>
Poem line one
Poem line two
Poem line three
Poem line four
Poem line five
Poem line six
</poem>
== Escaped poem test description ==
Test using alternate form of tag, via <nowiki>{{#tag:poem|}}</nowiki>. Transcluded test pages: [[Multipage poem test/a1|part a1]], [[Multipage poem test/b1|part b1]], [[Multipage poem test/c1|part c1]].
=== Test ===
{{:Multipage poem test/a1}}{{:Multipage poem test/b1}}{{:Multipage poem test/c1}}
=== Expected result ===
{{#tag:poem|
Poem line one
Poem line two
Poem line three
Poem line four
Poem line five
Poem line six
}}
== Custom div test description ==
Test circumventing any server-side special handling, by directly using <div class="poem"> and <br />. Transcluded test pages: [[Multipage poem test/a2|part a2]], [[Multipage poem test/b2|part b2]], [[Multipage poem test/c2|part c2]].
=== Test ===
{{:Multipage poem test/a2}}{{:Multipage poem test/b2}}{{:Multipage poem test/c2}}
=== Expected result ===
<div class="poem">
<p>Poem line one<br />
Poem line two<br />
Poem line three<br />
Poem line four<br />
Poem line five<br />
Poem line six</p>
</div>
1h65o1induczhz6d0zhvuum6euhjuap
Good redir
0
79145
737183
658319
2026-04-08T13:31:56Z
~2026-21715-52
73460
737183
wikitext
text/x-wiki
{{sil|İ1}}
#REDIRECT [[Test]]
#test
szktkcan5pi3npwuhn0lndjq41aet1y
Mavetuna
0
87468
737194
728429
2026-04-08T15:01:53Z
~2026-21606-01
73461
737194
wikitext
text/x-wiki
test on Dec 04 at 6:26pm EST by Vincatestdec04
test on Dec 08 at 4:08pm EST by Zilanttest08dec01
Anon edit Feb 10
My mentor - {{#mentor:Zilant63}}
== 100% map - frameless -full ==
Testing https://phabricator.wikimedia.org/T192251
test [[Timeline test|time]] zone preference
With 100% only.
<mapframe width="100%" height="400" latitude="40.325" longitude="-3.7172" text="Captions do appear" align="right" />
With frameless:
<mapframe width="100%" height="400" latitude="40.325" longitude="-3.7172" frameless />
<div style= "clear:both;"> </div>
Width=full
<mapframe width="full" height="400" latitude="40.325" longitude="-3.7172" text="width=full" />
== Section § 1 ==
<mapframe text="A geoshape of Alaska" width=300 height=300 zoom=3 latitude=64.01 longitude=-152.58 lang="ru">
{
"type": "ExternalData",
"service": "geoshape",
"ids": "Q797"
}
</mapframe>
Lorem ipsum dolor sit amet, Ã consectetur adipiscing elit. Nulla tellus dui, vestibulum nec sapien ac, interdum congue nibh. Maecenas pharetra, tellus id suscipit sodales, velit tellus varius metus, eu molestie justo elit sit amet nulla. Vivamus dolor lectus, malesuada eu varius efficitur, dapibus quis nunc. Suspendisse eu tellus erat. Aliquam accumsan venenatis ipsum, sed pulvinar urna viverra quis. Pellentesque non sapien id velit imperdiet pulvinar at eu est. Adding the link to [[MavetunaZilant17|MavetunaZilant]].
<mapframe width="300" height="300" zoom="12" latitude="40.782222" longitude="-73.965278">
[
{
"type": "ExternalData",
"service": "page",
"title": "Neighbourhoods/New York City.map"
},
{
"type": "ExternalData",
"service": "geoshape",
"ids": "Q160409",
"properties": {
"fill": "#07c63e", "title": "Central Park"
}
},
{
"type": "Feature",
"properties": {"title": "Roosevelt Island", "marker-color": "f01080"},
"geometry": {
"type": "Point",
"coordinates": [
-73.94511222839355,
40.76734665426719
]
}
}
]
</mapframe>
== £ Section ==
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tellus dui, vestibulum nec sapien ac, interdum congue nibh. Maecenas '''pharetra''', tellus id suscipit sodales, velit tellus varius metus, eu molestie justo elit sit amet nulla. Vivamus dolor lectus, malesuada eu varius efficitur, dapibus quis nunc. Suspendisse eu tellus erat. Aliquam accumsan venenatis ipsum, sed pulvinar urna viverra quis. Pellentesque non sapien id velit imperdiet pulvinar at eu est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
Suspendisse sed elementum urna, nec blandit sapien. Aliquam tincidunt viverra blandit.
{Mapframe|50.078|14.4180|zoom=12|staticmap=Prague_districts_en_wv.jpg}}
<div style="clear both;">
<mapframe latitude="46.7752" longitude="7.7333" zoom="9" width="350" height="300" text="Welcome to the Discovery Maps Team" align="right">
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"marker-symbol": "rocket",
"marker-color": "f00",
"marker-size": "large",
"title": "[https://www.mediawiki.org/w/index.php?title=Discovery Maps Team]"
},
"geometry": {
"type": "Point",
"coordinates": [
-122.4248,
37.7533
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.43155345321,
37.720416651143
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.00037360191,
38.000995277392
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.00067857451,
38.000277182198
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
7.6217651367188,
46.766205874237
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
7.904663085937501,
46.55886030311719
]
}
}
]
}
</mapframe>
</div>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean mollis vitae mi a auctor. Praesent non condimentum turpis. Curabitur consequat, nulla sed vestibulum commodo, ipsum dui finibus mi, ac imperdiet risus dui at dolor. Aliquam lacinia sem vel lectus sodales, sit amet tempor felis varius. Nunc lacinia dapibus lorem a aliquam. Fusce facilisis tempor vulputate. Aliquam interdum vitae velit non porttitor. Aenean ac semper lorem. Mauris dapibus mauris mattis, facilisis justo vel, cursus velit. Mauris sagittis turpis nibh, eu malesuada odio mollis id. Sed vitae finibus ex, ut aliquet velit. Integer nisi ipsum, gravida eget est vitae, dignissim vulputate est. Donec ut nibh sit amet ex volutpat bibendum pretium id metus. Etiam accumsan lobortis elit et condimentum. Test123.
Praesent blandit vehicula augue, sed mattis quam viverra non. In maximus, nisi non vestibulum finibus, lectus augue malesuada nulla, in venenatis libero quam a mauris. Fusce tincidunt orci quis nisl fringilla, id rhoncus dolor venenatis. Vestibulum vitae feugiat turpis. Proin a fringilla dui. Curabitur libero purus, feugiat in libero quis, cursus sodales turpis. Mauris eleifend erat ut lorem volutpat porttitor. Nulla mollis ut felis sed tincidunt. Pellentesque dolor tortor, semper vitae finibus nec, placerat et urna. Quisque id est justo.
In imperdiet odio sed risus maximus sodales. Donec feugiat pulvinar fringilla. Vestibulum a lorem quam. Ut nisl nisi, condimentum in sem a, rhoncus scelerisque nibh. Maecenas in congue urna. Morbi ligula felis, commodo venenatis est sed, porta pulvinar magna. Praesent auctor pulvinar diam. Etiam at diam eu nunc hendrerit suscipi
<!-- You will not be able to see this text. -->
<div style="clear both;">
<mapframe latitude="46.725" longitude="7.668" zoom="8" width="350" height="300" text="not edited in VE" align="right">
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {"marker-symbol": "rocket"},
"geometry": {
"type": "Point",
"coordinates": [
7.7106884121894845,
46.83984005391151
]
},
}
]
}
</mapframe>
</div>
== Section Ô ==
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tellus dui, vestibulum nec sapien ac, interdum congue nibh. Maecenas pharetra, tellus id suscipit sodales, velit tellus varius metus, eu molestie justo elit sit amet nulla. Vivamus dolor lectus, malesuada eu varius efficitur, dapibus quis nunc. Suspendisse eu tellus erat. Aliquam accumsan venenatis ipsum, sed pulvinar urna viverra quis. Pellentesque non sapien id velit imperdiet pulvinar at eu est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse sed elementum urna, nec blandit sapien. Aliquam tincidunt viverra blandit. [[User:Etonkovidova]] [[User:Zilant1|Zilant1]] ([[User talk:Zilant1|talk]]) 23:20, 7 February 2018 (UTC)
foobar
<div style="clear both;">
==Maps==
'''Modeling [https://tools.wmflabs.org/wikivoyage/w/poimap2.php?lat=51.47766&lon=-0.00115&zoom=auto&layer=W&lang=be&name=San_Francisco/Fisherman%27s_Wharf map] from the WikiVoyage's [https://en.wikivoyage.org/wiki/San_Francisco/Fisherman%27s_Wharf Fisherman's Wharf] '''
<mapframe width="650" height="400" zoom="14" longitude="-122.4305" latitude="37.7953" mode="interactive" show="poi" align="right">{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"stroke-width": 0.5,
"fill-opacity": 0.3
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
3600,
-180
],
[
3600,
180
],
[
-3600,
180
],
[
-3600,
-180
],
[
3600,
-180
]
],
[
[
-122.4267,
37.8081
],
[
-122.4265,
37.8082
],
[
-122.4264,
37.8077
],
[
-122.426,
37.8072
],
[
-122.4251,
37.8068
],
[
-122.4244,
37.8066
],
[
-122.4239,
37.8067
],
[
-122.4234,
37.8069
],
[
-122.4211,
37.8086
],
[
-122.4211,
37.8088
],
[
-122.4229,
37.8102
],
[
-122.4228,
37.8103
],
[
-122.4214,
37.8092
],
[
-122.4213,
37.8093
],
[
-122.421,
37.809
],
[
-122.4209,
37.8088
],
[
-122.4206,
37.8089
],
[
-122.4206,
37.8087
],
[
-122.4203,
37.8088
],
[
-122.4203,
37.8087
],
[
-122.4205,
37.8086
],
[
-122.4204,
37.8083
],
[
-122.4179,
37.8086
],
[
-122.4179,
37.8088
],
[
-122.42,
37.8089
],
[
-122.42,
37.8091
],
[
-122.4183,
37.8091
],
[
-122.4183,
37.8092
],
[
-122.4179,
37.809
],
[
-122.4178,
37.8086
],
[
-122.4175,
37.8086
],
[
-122.4175,
37.8084
],
[
-122.4174,
37.8083
],
[
-122.4174,
37.8081
],
[
-122.4163,
37.8083
],
[
-122.4163,
37.8089
],
[
-122.4175,
37.8089
],
[
-122.4174,
37.8091
],
[
-122.4206,
37.8113
],
[
-122.4203,
37.8115
],
[
-122.4193,
37.811
],
[
-122.4191,
37.8111
],
[
-122.4195,
37.8114
],
[
-122.4194,
37.8115
],
[
-122.4188,
37.8115
],
[
-122.4153,
37.8091
],
[
-122.4148,
37.8093
],
[
-122.4145,
37.8093
],
[
-122.4141,
37.809
],
[
-122.4128,
37.809
],
[
-122.4131,
37.8091
],
[
-122.4133,
37.8091
],
[
-122.4135,
37.8091
],
[
-122.4133,
37.8094
],
[
-122.4125,
37.8091
],
[
-122.4119,
37.8094
],
[
-122.4129,
37.8105
],
[
-122.4127,
37.8106
],
[
-122.4113,
37.8089
],
[
-122.4105,
37.8088
],
[
-122.4104,
37.8093
],
[
-122.4114,
37.8109
],
[
-122.4112,
37.8111
],
[
-122.4105,
37.8114
],
[
-122.4102,
37.8113
],
[
-122.41,
37.811
],
[
-122.4102,
37.8101
],
[
-122.4097,
37.8093
],
[
-122.4095,
37.8092
],
[
-122.4092,
37.8091
],
[
-122.4089,
37.8086
],
[
-122.4084,
37.8084
],
[
-122.4083,
37.8083
],
[
-122.4079,
37.8081
],
[
-122.4079,
37.8082
],
[
-122.4076,
37.8082
],
[
-122.4075,
37.8081
],
[
-122.4067,
37.8078
],
[
-122.4068,
37.81
],
[
-122.4061,
37.8101
],
[
-122.4059,
37.8072
],
[
-122.4053,
37.8069
],
[
-122.4044,
37.8089
],
[
-122.4039,
37.8087
],
[
-122.4027,
37.808
],
[
-122.4023,
37.8078
],
[
-122.4034,
37.8065
],
[
-122.4028,
37.8061
],
[
-122.4009,
37.8077
],
[
-122.4004,
37.8072
],
[
-122.401,
37.8036
],
[
-122.4007,
37.8034
],
[
-122.3986,
37.8046
],
[
-122.3982,
37.8042
],
[
-122.3999,
37.8032
],
[
-122.3996,
37.8029
],
[
-122.3978,
37.8038
],
[
-122.3975,
37.8035
],
[
-122.3997,
37.8022
],
[
-122.3994,
37.8018
],
[
-122.3971,
37.8031
],
[
-122.3961,
37.8019
],
[
-122.3984,
37.8007
],
[
-122.398,
37.8003
],
[
-122.3958,
37.8015
],
[
-122.3954,
37.8012
],
[
-122.3976,
37.7999
],
[
-122.3958,
37.7979
],
[
-122.3956,
37.7979
],
[
-122.3939,
37.799
],
[
-122.3936,
37.7986
],
[
-122.3952,
37.7977
],
[
-122.3948,
37.7973
],
[
-122.3932,
37.7983
],
[
-122.3929,
37.798
],
[
-122.3934,
37.7977
],
[
-122.3938,
37.7973
],
[
-122.3944,
37.797
],
[
-122.3943,
37.7969
],
[
-122.3947,
37.7966
],
[
-122.3958,
37.7967
],
[
-122.3986,
37.7999
],
[
-122.402,
37.8038
],
[
-122.4034,
37.8049
],
[
-122.406,
37.8065
],
[
-122.4253,
37.804
],
[
-122.4258,
37.8066
],
[
-122.4265,
37.8073
],
[
-122.4267,
37.8081
]
]
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.43438720703,
37.802460048863
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.41395950317,
37.792151101468
]
}
}
]
}
</mapframe>
</div>
__FORCETOC__
5hxpuwlvg5congmgmrvx1cl5hq1bj5p
737281
737194
2026-04-08T21:55:48Z
~2026-21834-82
73468
737281
wikitext
text/x-wiki
test on Dec 04 at 6:26pm EST by Vincatestdec04
test on Dec 08 at 4:08pm EST by Zilanttest08dec01
Anon edit Feb 10
Anon edit - April 08/2026
My mentor - {{#mentor:Zilant63}}
== 100% map - frameless -full ==
Testing https://phabricator.wikimedia.org/T192251
test [[Timeline test|time]] zone preference
With 100% only.
<mapframe width="100%" height="400" latitude="40.325" longitude="-3.7172" text="Captions do appear" align="right" />
With frameless:
<mapframe width="100%" height="400" latitude="40.325" longitude="-3.7172" frameless />
<div style= "clear:both;"> </div>
Width=full
<mapframe width="full" height="400" latitude="40.325" longitude="-3.7172" text="width=full" />
== Section § 1 ==
<mapframe text="A geoshape of Alaska" width=300 height=300 zoom=3 latitude=64.01 longitude=-152.58 lang="ru">
{
"type": "ExternalData",
"service": "geoshape",
"ids": "Q797"
}
</mapframe>
Lorem ipsum dolor sit amet, Ã consectetur adipiscing elit. Nulla tellus dui, vestibulum nec sapien ac, interdum congue nibh. Maecenas pharetra, tellus id suscipit sodales, velit tellus varius metus, eu molestie justo elit sit amet nulla. Vivamus dolor lectus, malesuada eu varius efficitur, dapibus quis nunc. Suspendisse eu tellus erat. Aliquam accumsan venenatis ipsum, sed pulvinar urna viverra quis. Pellentesque non sapien id velit imperdiet pulvinar at eu est. Adding the link to [[MavetunaZilant17|MavetunaZilant]].
<mapframe width="300" height="300" zoom="12" latitude="40.782222" longitude="-73.965278">
[
{
"type": "ExternalData",
"service": "page",
"title": "Neighbourhoods/New York City.map"
},
{
"type": "ExternalData",
"service": "geoshape",
"ids": "Q160409",
"properties": {
"fill": "#07c63e", "title": "Central Park"
}
},
{
"type": "Feature",
"properties": {"title": "Roosevelt Island", "marker-color": "f01080"},
"geometry": {
"type": "Point",
"coordinates": [
-73.94511222839355,
40.76734665426719
]
}
}
]
</mapframe>
== £ Section ==
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tellus dui, vestibulum nec sapien ac, interdum congue nibh. Maecenas '''pharetra''', tellus id suscipit sodales, velit tellus varius metus, eu molestie justo elit sit amet nulla. Vivamus dolor lectus, malesuada eu varius efficitur, dapibus quis nunc. Suspendisse eu tellus erat. Aliquam accumsan venenatis ipsum, sed pulvinar urna viverra quis. Pellentesque non sapien id velit imperdiet pulvinar at eu est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
Suspendisse sed elementum urna, nec blandit sapien. Aliquam tincidunt viverra blandit.
{Mapframe|50.078|14.4180|zoom=12|staticmap=Prague_districts_en_wv.jpg}}
<div style="clear both;">
<mapframe latitude="46.7752" longitude="7.7333" zoom="9" width="350" height="300" text="Welcome to the Discovery Maps Team" align="right">
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"marker-symbol": "rocket",
"marker-color": "f00",
"marker-size": "large",
"title": "[https://www.mediawiki.org/w/index.php?title=Discovery Maps Team]"
},
"geometry": {
"type": "Point",
"coordinates": [
-122.4248,
37.7533
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.43155345321,
37.720416651143
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.00037360191,
38.000995277392
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.00067857451,
38.000277182198
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
7.6217651367188,
46.766205874237
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
7.904663085937501,
46.55886030311719
]
}
}
]
}
</mapframe>
</div>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean mollis vitae mi a auctor. Praesent non condimentum turpis. Curabitur consequat, nulla sed vestibulum commodo, ipsum dui finibus mi, ac imperdiet risus dui at dolor. Aliquam lacinia sem vel lectus sodales, sit amet tempor felis varius. Nunc lacinia dapibus lorem a aliquam. Fusce facilisis tempor vulputate. Aliquam interdum vitae velit non porttitor. Aenean ac semper lorem. Mauris dapibus mauris mattis, facilisis justo vel, cursus velit. Mauris sagittis turpis nibh, eu malesuada odio mollis id. Sed vitae finibus ex, ut aliquet velit. Integer nisi ipsum, gravida eget est vitae, dignissim vulputate est. Donec ut nibh sit amet ex volutpat bibendum pretium id metus. Etiam accumsan lobortis elit et condimentum. Test123.
Praesent blandit vehicula augue, sed mattis quam viverra non. In maximus, nisi non vestibulum finibus, lectus augue malesuada nulla, in venenatis libero quam a mauris. Fusce tincidunt orci quis nisl fringilla, id rhoncus dolor venenatis. Vestibulum vitae feugiat turpis. Proin a fringilla dui. Curabitur libero purus, feugiat in libero quis, cursus sodales turpis. Mauris eleifend erat ut lorem volutpat porttitor. Nulla mollis ut felis sed tincidunt. Pellentesque dolor tortor, semper vitae finibus nec, placerat et urna. Quisque id est justo.
In imperdiet odio sed risus maximus sodales. Donec feugiat pulvinar fringilla. Vestibulum a lorem quam. Ut nisl nisi, condimentum in sem a, rhoncus scelerisque nibh. Maecenas in congue urna. Morbi ligula felis, commodo venenatis est sed, porta pulvinar magna. Praesent auctor pulvinar diam. Etiam at diam eu nunc hendrerit suscipi
<!-- You will not be able to see this text. -->
<div style="clear both;">
<mapframe latitude="46.725" longitude="7.668" zoom="8" width="350" height="300" text="not edited in VE" align="right">
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {"marker-symbol": "rocket"},
"geometry": {
"type": "Point",
"coordinates": [
7.7106884121894845,
46.83984005391151
]
},
}
]
}
</mapframe>
</div>
== Section Ô ==
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tellus dui, vestibulum nec sapien ac, interdum congue nibh. Maecenas pharetra, tellus id suscipit sodales, velit tellus varius metus, eu molestie justo elit sit amet nulla. Vivamus dolor lectus, malesuada eu varius efficitur, dapibus quis nunc. Suspendisse eu tellus erat. Aliquam accumsan venenatis ipsum, sed pulvinar urna viverra quis. Pellentesque non sapien id velit imperdiet pulvinar at eu est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse sed elementum urna, nec blandit sapien. Aliquam tincidunt viverra blandit. [[User:Etonkovidova]] [[User:Zilant1|Zilant1]] ([[User talk:Zilant1|talk]]) 23:20, 7 February 2018 (UTC)
foobar
<div style="clear both;">
==Maps==
'''Modeling [https://tools.wmflabs.org/wikivoyage/w/poimap2.php?lat=51.47766&lon=-0.00115&zoom=auto&layer=W&lang=be&name=San_Francisco/Fisherman%27s_Wharf map] from the WikiVoyage's [https://en.wikivoyage.org/wiki/San_Francisco/Fisherman%27s_Wharf Fisherman's Wharf] '''
<mapframe width="650" height="400" zoom="14" longitude="-122.4305" latitude="37.7953" mode="interactive" show="poi" align="right">{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"stroke-width": 0.5,
"fill-opacity": 0.3
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
3600,
-180
],
[
3600,
180
],
[
-3600,
180
],
[
-3600,
-180
],
[
3600,
-180
]
],
[
[
-122.4267,
37.8081
],
[
-122.4265,
37.8082
],
[
-122.4264,
37.8077
],
[
-122.426,
37.8072
],
[
-122.4251,
37.8068
],
[
-122.4244,
37.8066
],
[
-122.4239,
37.8067
],
[
-122.4234,
37.8069
],
[
-122.4211,
37.8086
],
[
-122.4211,
37.8088
],
[
-122.4229,
37.8102
],
[
-122.4228,
37.8103
],
[
-122.4214,
37.8092
],
[
-122.4213,
37.8093
],
[
-122.421,
37.809
],
[
-122.4209,
37.8088
],
[
-122.4206,
37.8089
],
[
-122.4206,
37.8087
],
[
-122.4203,
37.8088
],
[
-122.4203,
37.8087
],
[
-122.4205,
37.8086
],
[
-122.4204,
37.8083
],
[
-122.4179,
37.8086
],
[
-122.4179,
37.8088
],
[
-122.42,
37.8089
],
[
-122.42,
37.8091
],
[
-122.4183,
37.8091
],
[
-122.4183,
37.8092
],
[
-122.4179,
37.809
],
[
-122.4178,
37.8086
],
[
-122.4175,
37.8086
],
[
-122.4175,
37.8084
],
[
-122.4174,
37.8083
],
[
-122.4174,
37.8081
],
[
-122.4163,
37.8083
],
[
-122.4163,
37.8089
],
[
-122.4175,
37.8089
],
[
-122.4174,
37.8091
],
[
-122.4206,
37.8113
],
[
-122.4203,
37.8115
],
[
-122.4193,
37.811
],
[
-122.4191,
37.8111
],
[
-122.4195,
37.8114
],
[
-122.4194,
37.8115
],
[
-122.4188,
37.8115
],
[
-122.4153,
37.8091
],
[
-122.4148,
37.8093
],
[
-122.4145,
37.8093
],
[
-122.4141,
37.809
],
[
-122.4128,
37.809
],
[
-122.4131,
37.8091
],
[
-122.4133,
37.8091
],
[
-122.4135,
37.8091
],
[
-122.4133,
37.8094
],
[
-122.4125,
37.8091
],
[
-122.4119,
37.8094
],
[
-122.4129,
37.8105
],
[
-122.4127,
37.8106
],
[
-122.4113,
37.8089
],
[
-122.4105,
37.8088
],
[
-122.4104,
37.8093
],
[
-122.4114,
37.8109
],
[
-122.4112,
37.8111
],
[
-122.4105,
37.8114
],
[
-122.4102,
37.8113
],
[
-122.41,
37.811
],
[
-122.4102,
37.8101
],
[
-122.4097,
37.8093
],
[
-122.4095,
37.8092
],
[
-122.4092,
37.8091
],
[
-122.4089,
37.8086
],
[
-122.4084,
37.8084
],
[
-122.4083,
37.8083
],
[
-122.4079,
37.8081
],
[
-122.4079,
37.8082
],
[
-122.4076,
37.8082
],
[
-122.4075,
37.8081
],
[
-122.4067,
37.8078
],
[
-122.4068,
37.81
],
[
-122.4061,
37.8101
],
[
-122.4059,
37.8072
],
[
-122.4053,
37.8069
],
[
-122.4044,
37.8089
],
[
-122.4039,
37.8087
],
[
-122.4027,
37.808
],
[
-122.4023,
37.8078
],
[
-122.4034,
37.8065
],
[
-122.4028,
37.8061
],
[
-122.4009,
37.8077
],
[
-122.4004,
37.8072
],
[
-122.401,
37.8036
],
[
-122.4007,
37.8034
],
[
-122.3986,
37.8046
],
[
-122.3982,
37.8042
],
[
-122.3999,
37.8032
],
[
-122.3996,
37.8029
],
[
-122.3978,
37.8038
],
[
-122.3975,
37.8035
],
[
-122.3997,
37.8022
],
[
-122.3994,
37.8018
],
[
-122.3971,
37.8031
],
[
-122.3961,
37.8019
],
[
-122.3984,
37.8007
],
[
-122.398,
37.8003
],
[
-122.3958,
37.8015
],
[
-122.3954,
37.8012
],
[
-122.3976,
37.7999
],
[
-122.3958,
37.7979
],
[
-122.3956,
37.7979
],
[
-122.3939,
37.799
],
[
-122.3936,
37.7986
],
[
-122.3952,
37.7977
],
[
-122.3948,
37.7973
],
[
-122.3932,
37.7983
],
[
-122.3929,
37.798
],
[
-122.3934,
37.7977
],
[
-122.3938,
37.7973
],
[
-122.3944,
37.797
],
[
-122.3943,
37.7969
],
[
-122.3947,
37.7966
],
[
-122.3958,
37.7967
],
[
-122.3986,
37.7999
],
[
-122.402,
37.8038
],
[
-122.4034,
37.8049
],
[
-122.406,
37.8065
],
[
-122.4253,
37.804
],
[
-122.4258,
37.8066
],
[
-122.4265,
37.8073
],
[
-122.4267,
37.8081
]
]
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.43438720703,
37.802460048863
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-122.41395950317,
37.792151101468
]
}
}
]
}
</mapframe>
</div>
__FORCETOC__
aw63iztdgy5tf3i0n7m1tvn8v1wpacm
Group homomorphism
0
93576
737276
297464
2026-04-08T20:21:33Z
AsteraBot
69554
Bot: Adding {{reflist}}
737276
wikitext
text/x-wiki
[[File:Group_homomorphism_ver.2.svg|right|thumb|250x250px|Image of a group homomorphism ('''h''') from '''G''' (left) to '''H''' (right). The smaller oval inside '''H''' is the image of '''h'''. '''N''' is the kernel of '''h''' and '''aN''' is a [[coset]] of '''N'''.]]
{{Group theory sidebar|Basics}}
In [[mathematics]], given two [[Group (mathematics)|groups]], (''G'', ∗) and (''H'', ·), a '''group homomorphism''' from (''G'', ∗) to (''H'', ·) is a [[Function (mathematics)|function]] ''h'' : ''G'' → ''H'' such that for all ''u'' and ''v'' in ''G'' it holds that
: <math> h(u*v) = h(u) \cdot h(v) </math>
where the group operation on the left hand side of the equation is that of ''G'' and on the right hand side that of ''H''.
From this property, one can deduce that ''h'' maps the [[identity element]] ''e<sub>G</sub>'' of ''G'' to the identity element ''e<sub>H</sub>'' of ''H'', and it also maps inverses to inverses in the sense that
: <math> h\left(u^{-1}\right) = h(u)^{-1}. \,</math>
Hence one can say that ''h'' "is compatible with the group structure".
Older notations for the homomorphism ''h''(''x'') may be ''x''<sub>''h''</sub>, though this may be confused as an index or a general subscript. A more recent trend is to write group homomorphisms on the right of their arguments, omitting brackets, so that ''h''(''x'') becomes simply ''x h''. This approach is especially prevalent in areas of group theory where [[Automata theory|automata]] play a role, since it accords better with the convention that automata read words from left to right.
In areas of mathematics where one considers groups endowed with additional structure, a ''homomorphism'' sometimes means a map which respects not only the group structure (as above) but also the extra structure. For example, a homomorphism of [[Topological group|topological groups]] is often required to be continuous.
== References ==
{{reflist}}
0lupbpj5yuhjudo3ispp4grs18mm6j0
Foca de casc
0
95352
737227
689045
2026-04-08T18:13:58Z
Bad-editor-111
62134
Blanked the page
737227
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
Future
0
97347
737275
673328
2026-04-08T20:21:22Z
AsteraBot
69554
Bot: Adding {{reflist}}
737275
wikitext
text/x-wiki
The future has arrived, the future was alive. the future was alive and arrivedn Today.
== References ==
{{reflist}}
8nqfr0nfveouucqs30hta9pvj9axg63
User:Sam Sailor/test.js
2
98186
737164
737162
2026-04-08T12:00:38Z
Sam Sailor
26820
Test
737164
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(redirTitle).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
const api = new mw.Api();
const candidates = [`${name} (${type})`, `${name} (name)`, name];
for (const title of candidates) {
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const isNameMatch = /\{\{(Surname|Hndis|Given name)/i.test(text);
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(\|(surname|surnames|given name|given names))?\}\}/i.test(text);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) break;
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(title),
target: '_blank'
}).text(title)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(title),
target: '_blank'
}).text(title)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId) {
const api = new mw.Api();
const content = `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const nameParts = targetSortName.split(',');
const surname = nameParts[0].trim();
const givenName = nameParts.length > 1 ? nameParts[1].trim().split(' ')[0] : "";
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check both lists
if (givenName) {
await checkNameList(givenName, 'given name', currentTitle, hasShortDesc, isOrphan);
}
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
93e7gxpmcig7teqi7t7gh218b61ficm
737167
737164
2026-04-08T12:23:13Z
Sam Sailor
26820
Test
737167
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(redirTitle).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
const api = new mw.Api();
const candidates = [`${name} (${type})`, `${name} (name)`, name];
for (const title of candidates) {
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const isNameMatch = /\{\{(Surname|Hndis|Given name)/i.test(text);
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given name|given names)|(?:\s*\}\}))/i.test(text);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) break;
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(title),
target: '_blank'
}).text(title)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(title),
target: '_blank'
}).text(title)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId) {
const api = new mw.Api();
const content = `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const nameParts = targetSortName.split(',');
const surname = nameParts[0].trim();
const givenName = nameParts.length > 1 ? nameParts[1].trim().split(' ')[0] : "";
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check both lists
if (givenName) {
await checkNameList(givenName, 'given name', currentTitle, hasShortDesc, isOrphan);
}
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
rrgak9gf3uu89skc2lywcip11fo9isv
737168
737167
2026-04-08T12:38:27Z
Sam Sailor
26820
Test
737168
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(redirTitle).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) break;
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(title),
target: '_blank'
}).text(title)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(title),
target: '_blank'
}).text(title)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId) {
const api = new mw.Api();
const content = `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const nameParts = targetSortName.split(',');
const surname = nameParts[0].trim();
const givenName = nameParts.length > 1 ? nameParts[1].trim().split(' ')[0] : "";
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check both lists
if (givenName) {
await checkNameList(givenName, 'given name', currentTitle, hasShortDesc, isOrphan);
}
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
f10vq9k0h0vrwusk5ej42t8xeg5kdr1
737169
737168
2026-04-08T12:43:16Z
Sam Sailor
26820
Test
737169
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(redirTitle).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId) {
const api = new mw.Api();
const content = `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const nameParts = targetSortName.split(',');
const surname = nameParts[0].trim();
const givenName = nameParts.length > 1 ? nameParts[1].trim().split(' ')[0] : "";
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check both lists
if (givenName) {
await checkNameList(givenName, 'given name', currentTitle, hasShortDesc, isOrphan);
}
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
0w1ow96znz2motw38bcl8mvb3ikttrp
737176
737169
2026-04-08T13:16:55Z
Sam Sailor
26820
Test
737176
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(redirTitle).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId) {
const api = new mw.Api();
const content = `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const allParts = currentTitle.split(' ');
const givenNameCandidate = allParts[0]; // First word
const surnameCandidates = allParts.slice(1); // All subsequent words
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given name list
if (givenNameCandidate) {
await checkNameList(givenNameCandidate, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check every individual surname candidate found
for (const surname of surnameCandidates) {
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
0dsa7jlqku5qoawi13da4eyaksac3ij
737184
737176
2026-04-08T13:33:47Z
Sam Sailor
26820
Test
737184
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(redirTitle).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId) {
const api = new mw.Api();
const content = `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const allParts = currentTitle.split(' ');
const givenNameCandidate = allParts[0]; // First word
// Filter out initials
const surnameCandidates = allParts.slice(1).filter(part => {
return part.replace(/\./g, '').length > 1;
});
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given name list
if (givenNameCandidate) {
await checkNameList(givenNameCandidate, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check every individual surname candidate found
for (const surname of surnameCandidates) {
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
tq5ydsbowbepbodg2eq63chcgdgjv0y
737190
737184
2026-04-08T14:13:47Z
Sam Sailor
26820
Test
737190
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(redirTitle).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId) {
const api = new mw.Api();
const content = `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction ---
const leadLine = text.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes and normalize spaces
let longName = boldMatch[1].replace(/"[^"]*"/g, '').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const allParts = currentTitle.split(' ');
const givenNameCandidate = allParts[0]; // First word
// Filter out initials
const surnameCandidates = allParts.slice(1).filter(part => {
return part.replace(/\./g, '').length > 1;
});
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given name list
if (givenNameCandidate) {
await checkNameList(givenNameCandidate, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check every individual surname candidate found
for (const surname of surnameCandidates) {
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
tsocal1odqp27j8n38jpla6nw936uye
737192
737190
2026-04-08T14:23:32Z
Sam Sailor
26820
Test
737192
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes and normalize spaces
let longName = boldMatch[1].replace(/"[^"]*"/g, '').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let rCat = "R from sort name";
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `R from sort name|${lI}|${fI}`;
}
checkAndRenderRedirect(targetSortName, currentTitle, rCat, "Sort name");
// Extract names for list checking
const allParts = currentTitle.split(' ');
const givenNameCandidate = allParts[0]; // First word
// Filter out initials
const surnameCandidates = allParts.slice(1).filter(part => {
return part.replace(/\./g, '').length > 1;
});
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given name list
if (givenNameCandidate) {
await checkNameList(givenNameCandidate, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check every individual surname candidate found
for (const surname of surnameCandidates) {
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
dpvo8prks6h6ovej8iefk8ucdg6ezbr
737195
737192
2026-04-08T15:08:45Z
Sam Sailor
26820
Test
737195
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes and normalize spaces
let longName = boldMatch[1].replace(/"[^"]*"/g, '').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
// If the article has a parenthetical disambiguator, find baseName
if (currentTitle.includes(' (')) {
const baseName = currentTitle.split(' (')[0];
targetPage = baseName;
rTemplate = "R from ambiguous sort name";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// R to targetPage (which might be the DAB page)
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name");
// Extract names for list checking
const allParts = currentTitle.split(' ');
const givenNameCandidate = allParts[0]; // First word
// Filter out initials
const surnameCandidates = allParts.slice(1).filter(part => {
return part.replace(/\./g, '').length > 1;
});
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given name list
if (givenNameCandidate) {
await checkNameList(givenNameCandidate, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check every individual surname candidate found
for (const surname of surnameCandidates) {
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
f08dsfjic3u14r29cb8l04douonn0of
737201
737195
2026-04-08T15:35:01Z
Sam Sailor
26820
Test
737201
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes and normalize spaces
let longName = boldMatch[1].replace(/"[^"]*"/g, '').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let isAmbiguous = false;
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
isAmbiguous = true;
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// If ambiguous, let's verify the base name isn't already a DAB
if (isAmbiguous) {
const targetRes = await api.get({
action: 'query',
titles: targetPage,
prop: 'templates',
tltemplates: 'Template:Disambiguation', // Simplified check
formatversion: 2
});
const targetPageData = targetRes.query.pages[0];
if (!targetPageData.missing) {
// If the base name exists, we still offer the redirect,
// but we point it to the DAB page as you intended.
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name (ambiguous)");
}
} else {
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name");
}
// R to targetPage (which might be the DAB page)
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name");
// Extract names for list checking
const allParts = currentTitle.split(' ');
const givenNameCandidate = allParts[0]; // First word
// Filter out initials
const surnameCandidates = allParts.slice(1).filter(part => {
return part.replace(/\./g, '').length > 1;
});
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given name list
if (givenNameCandidate) {
await checkNameList(givenNameCandidate, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check every individual surname candidate found
for (const surname of surnameCandidates) {
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
rw5qx64efz5xf9s14lpzg6qudjvcgk7
737202
737201
2026-04-08T15:43:40Z
Sam Sailor
26820
Test
737202
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes and normalize spaces
let longName = boldMatch[1].replace(/"[^"]*"/g, '').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
// Extract names for list checking
const allParts = currentTitle.split(' ');
const givenNameCandidate = allParts[0]; // First word
// Filter out initials
const surnameCandidates = allParts.slice(1).filter(part => {
return part.replace(/\./g, '').length > 1;
});
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given name list
if (givenNameCandidate) {
await checkNameList(givenNameCandidate, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check every individual surname candidate found
for (const surname of surnameCandidates) {
await checkNameList(surname, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
9dnzyhc6tt7jctsb8962y20e5qek6dh
737203
737202
2026-04-08T16:25:51Z
Sam Sailor
26820
Test
737203
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const orphanPattern = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)[\s\S]*?\}\}/i;
const multiIssueParamRegex = /\| *orphan *= *[^|}]*/gi;
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
const miRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|([\s\S]*?)\}\}/g;
text = text.replace(miRegex, function(fullMatch, title, innerContent) {
let templates = innerContent.match(/\{\{[\s\S]*?\}\}/g) || [];
let survivors = templates.filter(t => !orphanPattern.test(t));
if (survivors.length === 0) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return '';
}
if (survivors.length === 1) {
summary += ' and redundant [[Template:Multiple issues|{{Multiple issues}}]]';
return survivors[0].trim();
}
return '{{' + title + '|\n' + survivors.map(s => s.trim()).join('\n') + '\n}}';
});
text = text.replace(new RegExp(orphanPattern, 'g'), '')
.replace(multiIssueParamRegex, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes and normalize spaces
let longName = boldMatch[1].replace(/"[^"]*"/g, '').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names from Wikidata
for (const name of givenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names from Wikidata
for (const name of familyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean list
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget))
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
$snippetWrapper.append($('<span>').text(snippet), $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet);
$(this).text('Copied!').prop('disabled', true);
}));
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold'
}).text('⚠️ Missing short description.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
mkbkip9xawjzi26vb85e8r6j4pd1zq0
737206
737203
2026-04-08T16:57:19Z
Sam Sailor
26820
Test
737206
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
// Remove the Orphan template from inside OR outside Multiple issues
text = text.replace(/\{\{Orphan(.*?)\}\}[\|\n]?/gi, '');
// Parse out old-style Multiple issues orphan parameters
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
// Cleanup: If Multiple issues is now empty or only has one template left
const miEmptyRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|\s*\}\}/gi;
text = text.replace(miEmptyRegex, '');
// Final trim and whitespace fix
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes and normalize spaces
let longName = boldMatch[1].replace(/"[^"]*"/g, '').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names from Wikidata
for (const name of givenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names from Wikidata
for (const name of familyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean list
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget))
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
$snippetWrapper.append($('<span>').text(snippet), $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet);
$(this).text('Copied!').prop('disabled', true);
}));
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold'
}).text('⚠️ Missing short description.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
7pfu8xkvx6fd5qanylv6ujjkxbci5aj
737208
737206
2026-04-08T17:06:08Z
Sam Sailor
26820
Test
737208
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle)));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $codeSpan = $('<span>').text(snippet).css('font-family', 'inherit');
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($codeSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
// Remove the Orphan template from inside OR outside Multiple issues
text = text.replace(/\{\{Orphan(.*?)\}\}[\|\n]?/gi, '');
// Parse out old-style Multiple issues orphan parameters
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
// Cleanup: If Multiple issues is now empty or only has one template left
const miEmptyRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|\s*\}\}/gi;
text = text.replace(miEmptyRegex, '');
// Final trim and whitespace fix
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1]
.replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names from Wikidata
for (const name of givenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names from Wikidata
for (const name of familyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean list
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget))
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
$snippetWrapper.append($('<span>').text(snippet), $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet);
$(this).text('Copied!').prop('disabled', true);
}));
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold'
}).text('⚠️ Missing short description.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
ipcbrbr76xh4z18wh6ylai9y8yaa0zv
737211
737208
2026-04-08T17:42:00Z
Sam Sailor
26820
Test
737211
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
redirects: 1 // Follow redirects to find the actual list page
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
})
);
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
// Remove the Orphan template from inside OR outside Multiple issues
text = text.replace(/\{\{Orphan(.*?)\}\}[\|\n]?/gi, '');
// Parse out old-style Multiple issues orphan parameters
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
// Cleanup: If Multiple issues is now empty or only has one template left
const miEmptyRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|\s*\}\}/gi;
text = text.replace(miEmptyRegex, '');
// Final trim and whitespace fix
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1]
.replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names from Wikidata
for (const name of givenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names from Wikidata
for (const name of familyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean list
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget))
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
$snippetWrapper.append($('<span>').text(snippet), $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet);
$(this).text('Copied!').prop('disabled', true);
}));
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold'
}).text('⚠️ Missing short description.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
gfnaidhd6o88k67wbuo2nbuneb2wq03
737213
737211
2026-04-08T17:57:08Z
Sam Sailor
26820
Test
737213
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
})
);
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
// Remove the Orphan template from inside OR outside Multiple issues
text = text.replace(/\{\{Orphan(.*?)\}\}[\|\n]?/gi, '');
// Parse out old-style Multiple issues orphan parameters
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
// Cleanup: If Multiple issues is now empty or only has one template left
const miEmptyRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|\s*\}\}/gi;
text = text.replace(miEmptyRegex, '');
// Final trim and whitespace fix
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1]
.replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names from Wikidata
for (const name of givenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names from Wikidata
for (const name of familyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean list
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget))
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
$snippetWrapper.append($('<span>').text(snippet), $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet);
$(this).text('Copied!').prop('disabled', true);
}));
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold'
}).text('⚠️ Missing short description.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
ofgwjdjq4v9y69jmqtu9kv60izvtgij
737236
737213
2026-04-08T18:27:54Z
Sam Sailor
26820
Test
737236
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
})
);
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
// Remove the Orphan template from inside OR outside Multiple issues
text = text.replace(/\{\{Orphan(.*?)\}\}[\|\n]?/gi, '');
// Parse out old-style Multiple issues orphan parameters
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
// Cleanup: If Multiple issues is now empty or only has one template left
const miEmptyRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|\s*\}\}/gi;
text = text.replace(miEmptyRegex, '');
// Final trim and whitespace fix
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1]
.replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names from Wikidata
for (const name of givenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names from Wikidata
for (const fullName of familyNames) {
// Split composite surnames and check all parts
const nameParts = fullName.split(' ');
const namesToCheck = new Set([fullName, ...nameParts]);
for (const name of namesToCheck) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean list
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?")
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
})
);
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
q1xqo1kmzvbznn1zsqktzwyb9p6h6v5
737240
737236
2026-04-08T18:38:39Z
Sam Sailor
26820
Test
737240
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
})
);
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
// Remove the Orphan template from inside OR outside Multiple issues
text = text.replace(/\{\{Orphan(.*?)\}\}[\|\n]?/gi, '');
// Parse out old-style Multiple issues orphan parameters
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
// Cleanup: If Multiple issues is now empty or only has one template left
const miEmptyRegex = /\{\{(Multiple issues|multiple issues|Multiple Issues)\s*\|\s*\}\}/gi;
text = text.replace(miEmptyRegex, '');
// Final trim and whitespace fix
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1]
.replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?")
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
})
);
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
879szmt789xf1hp9nurj3s13v7rviev
737271
737240
2026-04-08T20:14:23Z
Sam Sailor
26820
Test
737271
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
})
);
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*\|?\s*/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
text = text.replace(/\|\s*\}\}/g, '}}');
text = text.replace(/\{\{(Multiple[ _]issues)\s*\|\s*\|/gi, '{{$1|');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, name, content) {
const trimmedContent = content.trim();
if (!trimmedContent) return ''; // Empty: delete entirely
if (!trimmedContent.includes('|') || (trimmedContent.match(/\{\{/g) || []).length === 1) {
return trimmedContent;
}
return match;
});
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1]
.replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?")
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
})
);
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
ka0wkm1jcpnp4jw43jiebva6ezo0vup
737278
737271
2026-04-08T20:22:53Z
Sam Sailor
26820
Test
737278
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent
.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
})
.sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") ||
$(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
})
);
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]] tag';
text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*\|?\s*/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
text = text.replace(/\|\s*\}\}/g, '}}');
text = text.replace(/\{\{(Multiple[ _]issues)\s*\|\s*\|/gi, '{{$1|');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, name, content) {
const trimmedContent = content.trim();
if (!trimmedContent) return '';
const templateCount = (trimmedContent.match(/\{\{/g) || []).length;
if (templateCount === 1) {
return trimmedContent;
}
return match;
});
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1]
.replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") ||
entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append(
$('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?")
);
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append(
'If the Short description is good, consider adding ',
$('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
})
);
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
l1rcw6vncisoyzamluoplzsc5wcfw3v
737307
737278
2026-04-09T06:46:57Z
Sam Sailor
26820
Test
737307
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
}).sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
}));
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const pageName = mw.config.get('wgPageName');
$('.ambox-Orphan').fadeOut(400, function() {
$(this).remove();
});
try {
await api.edit(pageName, function(rev) {
let text = rev.content;
let summary = 'Article deOrphaned; removed [[Template:Orphan|{{Orphan}}]]';
text = text.replace(/\{\{Orphan(.*?)\}\}[|\n]?/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, miName, content) {
let cleanedContent = content.replace(/\|\s*\|/g, '|').trim();
const templateMatches = cleanedContent.match(/\{\{/g) || [];
const count = templateMatches.length;
if (count === 0) {
return '';
} else if (count === 1) {
summary += '; dismantled {{Multiple issues}} container';
return cleanedContent;
}
return match;
});
text = text.replace(/\|\s*\}\}/g, '}}').replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Done! Tag removed from page (locally).", {
type: 'success'
});
} catch (error) {
mw.notify("Failed to save edit: " + error, {
type: 'error'
});
}
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1].replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") || entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append($('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?"));
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
}));
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
eqtjj8t1alvprl4lzpxojv5va4xk9jc
737309
737307
2026-04-09T06:56:52Z
Sam Sailor
26820
Test
737309
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
}).sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
}));
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const pageName = mw.config.get('wgPageName');
const $orphanTag = $('.ambox-Orphan');
const $multipleIssues = $orphanTag.closest('.ambox-multiple_issues');
if ($multipleIssues.length > 0) {
const totalTags = $multipleIssues.find('.ambox').length;
if (totalTags <= 2) {
$multipleIssues.fadeOut(400, function() {
$(this).remove();
});
} else {
$orphanTag.fadeOut(400, function() {
$(this).remove();
});
}
} else {
$orphanTag.fadeOut(400, function() {
$(this).remove();
});
}
try {
await api.edit(pageName, function(rev) {
let text = rev.content;
let summary = 'Article deOrphaned; removed [[Template:Orphan|{{Orphan}}]]';
text = text.replace(/\{\{Orphan(.*?)\}\}[|\n]?/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, miName, content) {
let cleanedContent = content.replace(/\|\s*\|/g, '|').trim();
const templateMatches = cleanedContent.match(/\{\{/g) || [];
const count = templateMatches.length;
if (count === 0) {
return '';
} else if (count === 1) {
summary += '; dismantled {{Multiple issues}} container';
return cleanedContent;
}
return match;
});
text = text.replace(/\|\s*\}\}/g, '}}').replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Done! Page state synced with server.", {
type: 'success'
});
} catch (error) {
mw.notify("Background save failed: " + error, {
type: 'error'
});
}
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1].replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") || entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append($('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?"));
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
}));
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
ocrqsuorjci7ofb7aiyl8za1p8eqqvo
737310
737309
2026-04-09T07:10:14Z
Sam Sailor
26820
Test
737310
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
}).sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
}));
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const pageName = mw.config.get('wgPageName');
$('.loadinganimation').text('Saving changes...');
try {
await api.edit(pageName, function(rev) {
let text = rev.content;
let summary = 'Article deOrphaned; removed [[Template:Orphan|{{Orphan}}]]';
text = text.replace(/\{\{Orphan(.*?)\}\}[|\n]?/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, miName, content) {
let cleanedContent = content.replace(/\|\s*\|/g, '|').trim();
const templateMatches = cleanedContent.match(/\{\{/g) || [];
const count = templateMatches.length;
if (count === 0) {
return '';
} else if (count === 1) {
summary += '; dismantled {{Multiple issues}} container';
return cleanedContent;
}
return match;
});
text = text.replace(/\|\s*\}\}/g, '}}').replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Deorphaning complete!", {
type: 'success'
});
setTimeout(() => location.reload(), 700);
} catch (error) {
$('.loadinganimation').text('Error! See notification.');
mw.notify("Failed to save: " + error, {
type: 'error'
});
}
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1].replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") || entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append($('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?"));
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
}));
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
ofkttak71x5m2dvm3gwf7rr4eh6tz71
737311
737310
2026-04-09T08:43:33Z
Sam Sailor
26820
Test
737311
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
}).sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the ', $('<a>').attr({
'href': 'https://en.wikipedia.org/wiki/Wikipedia:Short_description',
'target': '_blank',
'rel': 'noopener noreferrer'
}).text('short description'), ' is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
}));
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'display': 'block',
'margin-top': '5px'
}).append('⚠️ Missing ', $('<a>').attr({
'href': 'https://en.wikipedia.org/wiki/Wikipedia:Short_description',
'target': '_blank',
'rel': 'noopener noreferrer'
}).css('color', '#d33')
.text('short description'), ': Please add a concise one before listing.'));
}
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const pageName = mw.config.get('wgPageName');
$('.loadinganimation').text('Saving changes...');
try {
await api.edit(pageName, function(rev) {
let text = rev.content;
let summary = 'Article deOrphaned; removed [[Template:Orphan|{{Orphan}}]]';
text = text.replace(/\{\{Orphan(.*?)\}\}[|\n]?/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, miName, content) {
let cleanedContent = content.replace(/\|\s*\|/g, '|').trim();
const templateMatches = cleanedContent.match(/\{\{/g) || [];
const count = templateMatches.length;
if (count === 0) {
return '';
} else if (count === 1) {
summary += '; dismantled {{Multiple issues}} container';
return cleanedContent;
}
return match;
});
text = text.replace(/\|\s*\}\}/g, '}}').replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Deorphaning complete!", {
type: 'success'
});
setTimeout(() => location.reload(), 700);
} catch (error) {
$('.loadinganimation').text('Error! See notification.');
mw.notify("Failed to save: " + error, {
type: 'error'
});
}
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1].replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") || entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append($('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?"));
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
}));
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
3qx35rboj79bkmhxckrekczq0lnvke9
737325
737311
2026-04-09T09:17:40Z
Sam Sailor
26820
Restored revision 737310 by [[Special:Contributions/Sam Sailor|Sam Sailor]] ([[User talk:Sam Sailor|talk]]): . (TwinkleGlobal)
737325
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
}).sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
}));
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const pageName = mw.config.get('wgPageName');
$('.loadinganimation').text('Saving changes...');
try {
await api.edit(pageName, function(rev) {
let text = rev.content;
let summary = 'Article deOrphaned; removed [[Template:Orphan|{{Orphan}}]]';
text = text.replace(/\{\{Orphan(.*?)\}\}[|\n]?/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, miName, content) {
let cleanedContent = content.replace(/\|\s*\|/g, '|').trim();
const templateMatches = cleanedContent.match(/\{\{/g) || [];
const count = templateMatches.length;
if (count === 0) {
return '';
} else if (count === 1) {
summary += '; dismantled {{Multiple issues}} container';
return cleanedContent;
}
return match;
});
text = text.replace(/\|\s*\}\}/g, '}}').replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Deorphaning complete!", {
type: 'success'
});
setTimeout(() => location.reload(), 700);
} catch (error) {
$('.loadinganimation').text('Error! See notification.');
mw.notify("Failed to save: " + error, {
type: 'error'
});
}
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1].replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") || entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append($('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?"));
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
}));
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
// </nowiki>
ofkttak71x5m2dvm3gwf7rr4eh6tz71
737326
737325
2026-04-09T11:15:35Z
Sam Sailor
26820
Test
737326
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
}).sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
}));
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
const pageName = mw.config.get('wgPageName');
$('.loadinganimation').text('Saving changes...');
try {
await api.edit(pageName, function(rev) {
let text = rev.content;
let summary = 'Article deOrphaned; removed [[Template:Orphan|{{Orphan}}]]';
text = text.replace(/\{\{Orphan(.*?)\}\}[|\n]?/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, miName, content) {
let cleanedContent = content.replace(/\|\s*\|/g, '|').trim();
const templateMatches = cleanedContent.match(/\{\{/g) || [];
const count = templateMatches.length;
if (count === 0) {
return '';
} else if (count === 1) {
summary += '; dismantled {{Multiple issues}} container';
return cleanedContent;
}
return match;
});
text = text.replace(/\|\s*\}\}/g, '}}').replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Deorphaning complete!", {
type: 'success'
});
setTimeout(() => location.reload(), 700);
} catch (error) {
$('.loadinganimation').text('Error! See notification.');
mw.notify("Failed to save: " + error, {
type: 'error'
});
}
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1].replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") || entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append($('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?"));
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
}));
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
if (['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) {
mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'mediawiki.util']).done(function() {
$(function() {
var $summary = $('#wpSummaryWidget');
if (!$summary.length) return;
var summaryWidget = OO.ui.infuse($summary);
var clearButton = new OO.ui.ButtonWidget({
icon: 'clear',
framed: false,
title: 'Clear edit summary',
flags: ['destructive']
});
clearButton.$element.css({
'position': 'absolute',
'right': '0',
'top': '0',
'z-index': '1'
});
clearButton.on('click', function() {
summaryWidget.setValue('');
summaryWidget.focus();
});
summaryWidget.$element.append(clearButton.$element);
summaryWidget.$element.find('input').css('padding-right', '2.5em');
});
});
}// </nowiki>
ch2iwwp96122ctlw4dkxmg2lns34ysl
737329
737326
2026-04-09T11:46:42Z
Sam Sailor
26820
Test
737329
javascript
text/javascript
// <nowiki>
(function() {
var Comrade = Comrade || {};
mw.util.addCSS(`
.ambox-Orphan { display: block !important; }
.comrade-container {
box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px;
padding: 10px 15px; margin: 10px 0; display: flex;
flex-direction: column; align-items: flex-start; gap: 8px;
border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5;
}
.comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; }
.comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; }
.comrade-info { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-status { background: #eaf3ff; border-left: 5px solid #36c; }
.comrade-container button:not(.comrade-dismiss) {
background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px;
padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px;
}
.comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; }
.comrade-dismiss {
background: transparent; border: none; color: #d33; font-weight: bold;
cursor: pointer; font-size: 0.9em;
}
.comrade-dismiss:hover { text-decoration: underline; }
.comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; }
`);
function appendDismiss($el) {
$('<button>').addClass('comrade-dismiss').text('Dismiss').click(function() {
$(this).closest('.comrade-container').fadeOut();
}).appendTo($el.find('.comrade-header'));
$("#siteSub").after($el);
}
async function checkAndRenderRedirect(redirTitle, targetTitle, rCategory, labelText, customWikitext) {
const api = new mw.Api();
const res = await api.get({
action: 'query',
titles: redirTitle,
formatversion: 2
});
if (res.query.pages[0].missing) {
const safeId = "comrade-redir-" + btoa(unescape(encodeURIComponent(redirTitle))).replace(/=/g, "");
const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId);
const $header = $('<div>').addClass('comrade-header');
const summary = `Created redirect from ${labelText.toLowerCase()} to [[${targetTitle}]]`;
// Pass the customWikitext through to the creation function
const $btn = $('<button>').text('Create redirect').click(() => createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext));
$header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn));
$container.append($header);
appendDismiss($container);
}
}
function checkChemicalRedirect(wikitext, currentTitle) {
if (!/\{\{\s*Chembox Properties/i.test(wikitext)) {
console.log("Comrade: No 'Chembox Properties' template found.");
return;
}
let formula = "";
const formulaMatch = wikitext.match(/\|\s*Formula\s*=\s*([^|\n}]+)/i);
if (formulaMatch) {
formula = formulaMatch[1].replace(/<\/?sub>/gi, "").trim();
console.log("Comrade: Found explicit Formula parameter:", formula);
} else {
console.log("Comrade: No explicit formula. Searching for individual elements...");
const chemboxSection = wikitext.match(/\{\{\s*Chembox Properties[\s\S]*?\}\}/i);
if (chemboxSection) {
const content = chemboxSection[0];
console.log("Comrade: Chembox content extracted:", content);
// Comprehensive list extracted from [[Template:Chembox Elements/molecular formula]]
const allElements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be', 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm', 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'D', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu', 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr', 'Lu', 'Lv', 'Mc', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd', 'Ne', 'Nh', 'Ni', 'No', 'Np', 'O', 'Og', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm', 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn', 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta', 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'Ts', 'U', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr'];
let data = {};
allElements.forEach(el => {
const elRegex = new RegExp(`\\|\\s*${el}\\s*=\\s*(\\d+)`, 'i');
const elMatch = content.match(elRegex);
if (elMatch) {
console.log(`Comrade: Match found for ${el}:`, elMatch[0]);
if (elMatch[1] !== "0") {
data[el] = elMatch[1] === "1" ? "" : elMatch[1];
}
}
});
console.log("Comrade: Final element data object:", data);
const elementsPresent = Object.keys(data);
if (elementsPresent.length > 0) {
let formulaArray = [];
const hasC = "C" in data;
// If carbon exists, C then H
if (hasC) {
formulaArray.push("C" + data["C"]);
if ("H" in data) formulaArray.push("H" + data["H"]);
}
// Alphabetical for everything else
const remaining = elementsPresent.filter(el => {
if (hasC && (el === "C" || el === "H")) return false;
return true;
}).sort();
// Special case: If NO carbon, H must be sorted alphabetically
remaining.forEach(el => {
formulaArray.push(el + data[el]);
});
formula = formulaArray.join('');
console.log("Comrade: Generated formula from elements:", formula);
}
} else {
console.log("Comrade: Failed to extract Chembox Properties section content.");
}
}
if (formula && formula !== currentTitle) {
console.log(`Comrade: Success! Triggering redirect for ${formula}`);
checkAndRenderRedirect(formula, currentTitle, "R from chemical formula", "Chemical formula");
} else {
console.log("Comrade: Formula matches current title or is empty. No redirect needed.");
}
}
async function checkDomainRedirect(qid, currentTitle) {
let domainCandidate = "";
// Try to get the URL from Wikidata (P856)
if (qid) {
try {
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetclaims',
entity: qid,
property: 'P856',
format: 'json',
origin: '*'
}
});
if (res.claims?.P856) {
domainCandidate = res.claims.P856[0].mainsnak.datavalue.value;
console.log("Comrade: Found domain candidate in Wikidata:", domainCandidate);
}
} catch (e) {
console.log("Comrade: Wikidata API call failed.");
}
}
// Fallback to Infobox URL if Wikidata is empty
if (!domainCandidate) {
domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href");
if (domainCandidate) console.log("Comrade: Found domain candidate in Infobox:", domainCandidate);
}
if (domainCandidate) {
try {
const url = new URL(domainCandidate);
let domain = url.hostname.replace(/^www\d*\./, "");
// Only proceed if it's a root domain (pathname is "/")
if (domain.includes(".") && url.pathname === "/") {
console.log(`Comrade: Verifying if ${domainCandidate} is live via Citation API...`);
// The live check, CORS-friendly proxy
const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate);
$.ajax({
url: citationURL,
method: 'GET',
success: function() {
console.log(`Comrade: URL is live. Proceeding with redirect for: ${domain}`);
checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain");
},
error: function(xhr) {
console.log(`Comrade: URL failed live check (Status: ${xhr.status}). Skipping redirect.`);
}
});
} else {
console.log(`Comrade: ${domain} rejected (not a root domain or missing TLD).`);
}
} catch (e) {
console.log("Comrade: Error parsing URL object.");
}
} else {
console.log("Comrade: No domain candidate found for this article.");
}
}
async function checkNameList(name, type, currentTitle, hasShortDesc, isOrphan) {
console.log(`[Comrade] Checking ${type} list for: ${name}`);
const api = new mw.Api();
// Added lowercase version of the type to the candidates
const candidates = [`${name} (${type})`, `${name} (${type.toLowerCase()})`, `${name} (name)`, name];
// Set to keep track of titles we've already checked to avoid redundant API calls
const checked = new Set();
for (const title of candidates) {
if (checked.has(title)) continue;
checked.add(title);
console.log(`[Comrade] Querying: ${title}`);
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
formatversion: 2,
// redirects: 1 // Follow redirects to find the actual list page
redirects: 0 // Do not follow redirects
});
const page = res.query.pages[0];
if (page && !page.missing) {
const text = page.revisions[0].content;
const resolvedTitle = page.title;
// Check for specific name templates
const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text);
// Check for disambiguation pages with name parameters
const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text);
console.log(`[Comrade] Results for ${resolvedTitle} - Match: ${isNameMatch}, Dab: ${isNameDab}`);
if (isNameMatch || isNameDab) {
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text);
if (alreadyListed) {
console.log(`[Comrade] ${currentTitle} is already listed on ${resolvedTitle}`);
break;
}
const $container = $('<div>').addClass('comrade-container');
const $header = $('<div>').addClass('comrade-header');
const $content = $('<div>').addClass('comrade-content');
if (isOrphan) {
$container.addClass('comrade-nudge');
$header.append($('<strong>').text('Comrade nudge:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
} else {
$container.addClass('comrade-info');
$header.append($('<strong>').text('Informative:'));
$content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(resolvedTitle),
target: '_blank'
}).text(resolvedTitle), "; should it be?"));
}
const snippet = '* {{anbl|' + currentTitle + '}}';
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px'
}));
const $copyBtn = $('<button>').text('Copy').css('margin-left', '0').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
const originalText = $this.text();
$this.text('Copied!').prop('disabled', true);
setTimeout(() => {
$this.text(originalText).prop('disabled', false);
}, 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) {
$content.append($('<span>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
}
$container.append($header, $content);
appendDismiss($container);
break;
}
}
}
}
async function createModernRedirect(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) {
const api = new mw.Api();
// Use custom wikitext if provided (for Long Name), otherwise use standard template
const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`;
const summary = customSummary || `Created redirect from [[${redirTitle}]]`;
return api.postWithToken('csrf', {
action: 'edit',
title: redirTitle,
text: content,
summary: summary,
createonly: true
}).done(() => {
mw.notify("Redirect created!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
$(`#${elementId}`).fadeOut();
});
}
async function performDeorphan() {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
let summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]';
text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*\|?\s*/gi, '');
text = text.replace(/\| *Orphan *=[\d\w\s\n]*(.*?\}\})/gi, '$1');
text = text.replace(/\|\s*\}\}/g, '}}');
text = text.replace(/\{\{(Multiple[ _]issues)\s*\|\s*\|/gi, '{{$1|');
const miRegex = /\{\{(Multiple[ _]issues)\s*\|\s*([\s\S]*?)\s*\}\}/gi;
text = text.replace(miRegex, function(match, name, content) {
const trimmedContent = content.trim();
if (!trimmedContent) return '';
const templateCount = (trimmedContent.match(/\{\{/g) || []).length;
if (templateCount === 1) {
return trimmedContent;
}
return match;
});
text = text.replace(/\n{3,}/g, '\n\n').trim();
return {
text: text,
summary: summary,
minor: true
};
});
mw.notify("Orphan tag removed!", {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 700);
}
async function performSortCorrection(newSort, reason) {
const api = new mw.Api();
await api.edit(mw.config.get('wgPageName'), function(rev) {
let text = rev.content;
const dsRegex = /\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/gi;
text = text.replace(dsRegex, `{{DEFAULTSORT:${newSort}}}`);
return {
text: text,
summary: `Corrected [[Template:DEFAULTSORT|{{DEFAULTSORT}}]] ${reason}`,
minor: true
};
});
mw.notify(`DEFAULTSORT ${reason} corrected!`, {
type: 'success',
tag: 'comrade',
classes: ['comrade-success']
});
setTimeout(() => location.reload(), 1100);
}
function stripDiacritics(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
if (mw.config.get("wgNamespaceNumber") === 0 && mw.config.get("wgAction") === "view") {
$(document).ready(async function() {
const api = new mw.Api();
const qid = mw.config.get("wgWikibaseItemId");
try {
const [pageData, backlinkData] = await Promise.all([
api.get({
action: 'query',
prop: 'revisions',
titles: mw.config.get("wgPageName"),
rvprop: 'content',
formatversion: 2
}),
api.get({
action: 'query',
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 50,
blnamespace: 0,
bltitle: mw.config.get("wgPageName"),
formatversion: 2
})
]);
const page = pageData.query.pages[0];
if (!page || page.missing || !page.revisions) return;
const wikitext = page.revisions[0].content;
const currentTitle = mw.config.get("wgTitle");
const cleanTitle = stripDiacritics(currentTitle);
const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext);
const backLinkCount = backlinkData.query.backlinks.length;
if (isOrphanTagged && backLinkCount >= 1) {
const $container = $('<div>').addClass('comrade-container comrade-status');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text('Remove orphan tag').click(() => performDeorphan());
$header.append($('<div>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn));
$container.append($header);
appendDismiss($container);
}
if (cleanTitle !== currentTitle) checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic");
checkChemicalRedirect(wikitext, currentTitle);
checkDomainRedirect(qid, currentTitle);
if (qid) {
const wikiData = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: qid,
props: 'claims|labels',
languages: 'en',
format: 'json',
origin: '*'
},
dataType: 'json'
});
const entity = wikiData.entities[qid];
const isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext);
const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i);
let targetSortName = "";
if (dsMatch) {
const currentDS = dsMatch[1].trim();
const cleanDS = stripDiacritics(currentDS);
targetSortName = cleanDS;
if (currentDS !== cleanDS) {
const $dsDiacritics = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${cleanDS}`).click(() => performSortCorrection(cleanDS, "diacritics"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Key contains diacritics.`, $btn));
$dsDiacritics.append($header);
appendDismiss($dsDiacritics);
}
if (isHuman && !currentDS.includes(',')) {
const parts = cleanDS.split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
const $dsFormat = $('<div>').addClass('comrade-container comrade-nudge');
const $header = $('<div>').addClass('comrade-header');
const $btn = $('<button>').text(`Correct to: ${targetSortName}`).click(() => performSortCorrection(targetSortName, "format"));
$header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Structure should be "Last, First".`, $btn));
$dsFormat.append($header);
appendDismiss($dsFormat);
}
}
} else if (isHuman) {
const label = entity.labels?.en?.value || currentTitle;
const parts = stripDiacritics(label).split(' ');
if (parts.length >= 2) {
const last = parts.pop();
targetSortName = `${last}, ${parts.join(' ')}`;
}
}
// --- Long name extraction (using wikitext) ---
const leadLine = wikitext.split('\n').find(line => line.includes("'''"));
const boldMatch = leadLine ? leadLine.match(/'''([^']+)'''/) : null;
if (boldMatch && isHuman) {
// Clean nicknames in quotes "" and parentheses () and normalize spaces
let longName = boldMatch[1].replace(/\s*(\"[^\"]*\"|\([^\)]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim();
if (longName.length > currentTitle.length) {
const nameParts = longName.split(' ');
if (nameParts.length > 1) {
const surname = nameParts.pop();
const givenNames = nameParts.join(' ');
const sortKey = `${surname}, ${givenNames}`;
// Format the special redirect wikitext for the long name
const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`;
// Check if redirect exists and render if missing
checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext);
}
}
}
// --- Sort name ---
if (isHuman && targetSortName) {
let targetPage = currentTitle;
let rTemplate = "R from sort name";
let labelSuffix = "";
// If the article has a parenthetical disambiguator, find basename
if (currentTitle.includes(' (')) {
targetPage = currentTitle.split(' (')[0];
rTemplate = "R from ambiguous sort name";
labelSuffix = " (ambiguous)";
}
let rCat = rTemplate;
if (targetSortName.includes(',')) {
const parts = targetSortName.split(',');
const lI = parts[0].trim().charAt(0).toUpperCase();
const fI = parts[1].trim().charAt(0).toUpperCase();
if (lI && fI) rCat = `${rTemplate}|${lI}|${fI}`;
}
// Only run checkAndRender once per person
checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name" + labelSuffix);
}
// --- Name list checking via Wikidata P734/P735 ---
if (isHuman) {
const familyNameClaims = entity.claims?.P734 || [];
const givenNameClaims = entity.claims?.P735 || [];
// Check for Korean ethnicity (Q14972) or Korean citizenships (South Q884 / North Q424)
const isKorean = entity.claims?.P172?.some(c => c.mainsnak?.datavalue?.value?.id === "Q14972") || entity.claims?.P27?.some(c => ["Q884", "Q424"].includes(c.mainsnak?.datavalue?.value?.id));
const getNameLabels = async (claims) => {
const ids = claims.map(c => c.mainsnak.datavalue.value.id);
if (ids.length === 0) return [];
const res = await $.ajax({
url: "https://www.wikidata.org/w/api.php",
data: {
action: 'wbgetentities',
ids: ids.join('|'),
props: 'labels',
languages: 'en',
languagefallback: true,
format: 'json',
origin: '*'
}
});
return Object.values(res.entities).map(e => e.labels?.en?.value).filter(Boolean);
};
const [familyNames, givenNames] = await Promise.all([
getNameLabels(familyNameClaims),
getNameLabels(givenNameClaims)
]);
// Create sets to store final names to check (prevents duplicates)
const finalFamilyNames = new Set();
const finalGivenNames = new Set();
// Add Wikidata results, splitting composite family names
familyNames.forEach(fn => {
fn.split(' ').forEach(part => finalFamilyNames.add(part));
finalFamilyNames.add(fn);
});
givenNames.forEach(gn => finalGivenNames.add(gn));
// If Wikidata name data is missing, guess from the article title
if (finalFamilyNames.size === 0 || finalGivenNames.size === 0) {
// Strip disambiguators like "(politician)" or "(general)"
const cleanTitle = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim();
const titleParts = cleanTitle.split(' ');
if (titleParts.length >= 2) {
const guessedSurname = titleParts[titleParts.length - 1];
const guessedGiven = titleParts[0];
if (finalFamilyNames.size === 0) finalFamilyNames.add(guessedSurname);
if (finalGivenNames.size === 0) finalGivenNames.add(guessedGiven);
}
}
const isOrphan = isOrphanTagged && backLinkCount < 1;
// Check given names
for (const name of finalGivenNames) {
await checkNameList(name, 'given name', currentTitle, hasShortDesc, isOrphan);
}
// Check family names
for (const name of finalFamilyNames) {
let customTarget = null;
if (isKorean) {
if (name === "Lee") {
customTarget = "List of people with the Korean family name Lee";
} else if (name === "Kim") {
customTarget = "List of people with the Korean family name Kim";
}
}
if (customTarget) {
// Manual check for the specialized Korean lists
const listRes = await api.get({
action: 'query',
prop: 'revisions',
titles: customTarget,
rvprop: 'content',
formatversion: 2
});
const listPage = listRes.query.pages[0];
if (listPage && !listPage.missing) {
const listText = listPage.revisions[0].content;
const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]');
const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(listText);
if (!alreadyListed) {
const $container = $('<div>').addClass('comrade-container comrade-info');
const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Informative:'));
const $content = $('<div>').addClass('comrade-content').append($('<span>').append(`Surname not currently listed at `, $('<a>').attr({
href: mw.util.getUrl(customTarget),
target: '_blank'
}).text(customTarget), "; should it be?"));
const snippet = `* {{anbl|${currentTitle}}}`;
const $snippetWrapper = $('<div>').css({
'display': 'flex',
'align-items': 'center',
'gap': '10px',
'margin-top': '4px'
});
const $instructionSpan = $('<span>').append('If the Short description is good, consider adding ', $('<code>').text(snippet).css({
'background-color': '#f8f9fa',
'border': '1px solid #eaecf0',
'border-radius': '2px',
'padding': '1px 4px',
'font-family': 'monospace'
}));
const $copyBtn = $('<button>').text('Copy').click(function() {
navigator.clipboard.writeText(snippet).then(() => {
const $this = $(this);
$this.text('Copied!').prop('disabled', true);
setTimeout(() => $this.text('Copy').prop('disabled', false), 1500);
});
});
$snippetWrapper.append($instructionSpan, $copyBtn);
$content.append($snippetWrapper);
if (!hasShortDesc) $content.append($('<div>').css({
'color': '#d33',
'font-weight': 'bold',
'margin-top': '5px'
}).text('⚠️ Missing short description: Please add a concise one before listing.'));
$container.append($header, $content);
appendDismiss($container);
}
}
} else {
await checkNameList(name, 'surname', currentTitle, hasShortDesc, isOrphan);
}
}
}
}
} catch (err) {
console.error("Comrade error:", err);
}
});
}
})();
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "1.1.1",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 5,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
if (['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) {
mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'mediawiki.util']).done(function() {
$(function() {
var $summary = $('#wpSummaryWidget');
if (!$summary.length) return;
var summaryWidget = OO.ui.infuse($summary);
var clearButton = new OO.ui.ButtonWidget({
icon: 'clear',
framed: false,
title: 'Clear edit summary',
flags: ['destructive']
});
clearButton.$element.css({
'position': 'absolute',
'right': '0',
'top': '0',
'z-index': '1'
});
clearButton.on('click', function() {
summaryWidget.setValue('');
summaryWidget.focus();
});
summaryWidget.$element.append(clearButton.$element);
summaryWidget.$element.find('input').css('padding-right', '2.5em');
});
});
}
// </nowiki>
lh31frjlo9aou4bynbb49rzyisfrjzf
Example
0
106169
737232
674426
2026-04-08T18:15:12Z
~2026-21529-98
73464
AutoModerator test
737232
wikitext
text/x-wiki
'''This is an example of an article.'''
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eleifend donec pretium vulputate sapien nec sagittis. Cras pulvinar mattis nunc sed blandit libero. Amet nulla facilisi morbi tempus. Vitae ultricies leo integer malesuada nunc vel. Eget est lorem ipsum dolor sit amet consectetur adipiscing. Vitae congue eu consequat ac felis donec et odio pellentesque. Pellentesque sit amet porttitor eget dolor morbi non. Amet luctus venenatis lectus magna fringilla. Libero volutpat sed cras ornare arcu dui vivamus. Quam id leo in vitae turpis massa sed elementum. Libero justo laoreet sit amet cursus. Diam sit amet nisl suscipit adipiscing bibendum est. Habitant morbi tristique senectus et. Tempus iaculis urna id volutpat lacus.
Condimentum mattis pellentesque id nibh tortor id. Odio euismod lacinia at quis risus. Elit sed vulputate mi sit amet mauris commodo quis. Aliquet risus feugiat in ante metus dictum at tempor commodo. Urna nunc id cursus metus aliquam eleifend. Commodo odio aenean sed adipiscing diam. Scelerisque in dictum non consectetur a erat nam at lectus. Mattis pellentesque id nibh tortor. Duis convallis convallis tellus id interdum velit laoreet id donec. Vitae tempus quam pellentesque nec. Scelerisque mauris pellentesque pulvinar pellentesque habitant. At tellus at urna condimentum mattis pellentesque id nibh. Ut porttitor leo a diam sollicitudin tempor id eu. Vel orci porta non pulvinar neque laoreet suspendisse. Sem fringilla ut morbi tincidunt augue interdum velit euismod.
dky8p7vkutdmykyboje4owsgcalo4ny
737233
737232
2026-04-08T18:15:15Z
AutoModeratorTest
61468
Reverted edit by [[Special:Contributions/~2026-21529-98|~2026-21529-98]] ([[User talk:~2026-21529-98|talk]]) to last revision by [[User:~2025-64214-6|~2025-64214-6]]
674426
wikitext
text/x-wiki
'''This is an example of an article.'''
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eleifend donec pretium vulputate sapien nec sagittis. Cras pulvinar mattis nunc sed blandit libero. Amet nulla facilisi morbi tempus. Vitae ultricies leo integer malesuada nunc vel. Eget est lorem ipsum dolor sit amet consectetur adipiscing. Vitae congue eu consequat ac felis donec et odio pellentesque. Pellentesque sit amet porttitor eget dolor morbi non. Amet luctus venenatis lectus magna fringilla. Libero volutpat sed cras ornare arcu dui vivamus. Quam id leo in vitae turpis massa sed elementum. Libero justo laoreet sit amet cursus. Diam sit amet nisl suscipit adipiscing bibendum est. Habitant morbi tristique senectus et. Tempus iaculis urna id volutpat lacus.
Condimentum mattis pellentesque id nibh tortor id. Odio euismod lacinia at quis risus. Elit sed vulputate mi sit amet mauris commodo quis. Aliquet risus feugiat in ante metus dictum at tempor commodo. Urna nunc id cursus metus aliquam eleifend. Commodo odio aenean sed adipiscing diam. Scelerisque in dictum non consectetur a erat nam at lectus. Mattis pellentesque id nibh tortor. Duis convallis convallis tellus id interdum velit laoreet id donec. Vitae tempus quam pellentesque nec. Scelerisque mauris pellentesque pulvinar pellentesque habitant. At tellus at urna condimentum mattis pellentesque id nibh. Ut porttitor leo a diam sollicitudin tempor id eu. Vel orci porta non pulvinar neque laoreet suspendisse. Sem fringilla ut morbi tincidunt augue interdum velit euismod.
Mauris pharetra et ultrices neque ornare. Justo laoreet sit amet cursus sit amet dictum sit amet. Id semper risus in hendrerit gravida rutrum quisque non. Vulputate dignissim suspendisse in est ante in nibh. Ut venenatis tellus in metus. Tellus molestie nunc non blandit. Vulputate enim nulla aliquet porttitor lacus luctus accumsan tortor. In hac habitasse platea dictumst vestibulum rhoncus est pellentesque elit. Donec adipiscing tristique risus nec feugiat in fermentum. Amet venenatis urna cursus eget nunc scelerisque viverra mauris in. Rhoncus est pellentesque elit ullamcorper dignissim cras.
Nisl condimentum id venenatis a condimentum vitae sapien. Amet nulla facilisi morbi tempus. Aliquam etiam erat velit scelerisque in dictum non. Libero volutpat sed cras ornare arcu. Sed viverra ipsum nunc aliquet. In iaculis nunc sed augue lacus viverra. Non arcu risus quis varius quam. Id ornare arcu odio ut. Egestas purus viverra accumsan in nisl nisi scelerisque. Massa id neque aliquam vestibulum morbi. Ac felis donec et odio pellentesque. Condimentum id venenatis a condimentum vitae sapien pellentesque. Magna eget est lorem ipsum. Vivamus arcu felis bibendum ut tristique et egestas quis. Consequat id porta nibh venenatis cras sed felis. Et tortor at risus viverra adipiscing at in tellus integer.
Ultrices vitae auctor eu augue ut lectus. Amet cursus sit amet dictum sit amet justo donec. Fames ac turpis egestas sed tempus urna. Vitae et leo duis ut diam. Massa id neque aliquam vestibulum morbi blandit cursus. Enim ut sem viverra aliquet eget. In hac habitasse platea dictumst vestibulum rhoncus est pellentesque. Auctor urna nunc id cursus. Id neque aliquam vestibulum morbi blandit cursus risus at. Mauris cursus mattis molestie a iaculis at.
Tortor aliquam nulla facilisi cras fermentum. Turpis egestas maecenas pharetra convallis. Tellus in metus vulputate eu. Mattis molestie a iaculis at erat pellentesque. Mi quis hendrerit dolor magna eget est. Pulvinar etiam non quam lacus suspendisse faucibus interdum posuere lorem. Nisl purus in mollis nunc sed id semper risus. Mi bibendum neque egestas congue. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Sit amet porttitor eget dolor. Mi bibendum neque egestas congue quisque egestas diam.
Egestas pretium aenean pharetra magna ac. Ante metus dictum at tempor commodo ullamcorper. Enim sed faucibus turpis in eu mi bibendum neque egestas. Blandit cursus risus at ultrices. Sem nulla pharetra diam sit amet. Congue eu consequat ac felis donec et. Pellentesque nec nam aliquam sem et tortor consequat. Fames ac turpis egestas integer. Adipiscing commodo elit at imperdiet dui. Eget duis at tellus at urna condimentum. Massa sed elementum tempus egestas. Hendrerit dolor magna eget est lorem ipsum dolor. Penatibus et magnis dis parturient montes nascetur ridiculus.
Tortor pretium viverra suspendisse potenti. Varius duis at consectetur lorem donec massa sapien. Pellentesque elit eget gravida cum sociis. Amet cursus sit amet dictum sit amet justo. Mi eget mauris pharetra et ultrices. Pulvinar etiam non quam lacus suspendisse faucibus. Arcu dui vivamus arcu felis bibendum ut tristique et egestas. Sit amet luctus venenatis lectus magna fringilla urna. Nascetur ridiculus mus mauris vitae ultricies leo. Quis auctor elit sed vulputate mi sit amet mauris.
Vel pretium lectus quam id leo. Sodales ut eu sem integer vitae justo. Tincidunt eget nullam non nisi. Odio euismod lacinia at quis risus sed vulputate odio ut. Maecenas ultricies mi eget mauris pharetra et. Sit amet aliquam id diam maecenas ultricies mi eget mauris. Cursus turpis massa tincidunt dui ut ornare lectus. At tellus at urna condimentum. Amet cursus sit amet dictum sit amet. Habitant morbi tristique senectus et netus et malesuada.
Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Senectus et netus et malesuada fames ac turpis egestas. Vivamus arcu felis bibendum ut tristique et egestas. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Viverra tellus in hac habitasse platea dictumst vestibulum. Sollicitudin nibh sit amet commodo nulla facilisi nullam. Quis enim lobortis scelerisque fermentum dui. Odio morbi quis commodo odio aenean. Fermentum iaculis eu non diam phasellus. Lacus laoreet non curabitur gravida arcu ac tortor dignissim. Viverra maecenas accumsan lacus vel facilisis volutpat. Sem viverra aliquet eget sit amet.
eiib1n2qqzim48t3mbjl9fe5j76s6y5
737312
737233
2026-04-09T08:53:39Z
PieWriter
72123
737312
wikitext
text/x-wiki
'''This is an example of an article.'''
Egestas pretium aenean pharetra magna ac. Ante metus dictum at tempor commodo ullamcorper. Enim sed faucibus turpis in eu mi bibendum neque egestas. Blandit cursus risus at ultrices. Sem nulla pharetra diam sit amet. Congue eu consequat ac felis donec et. Pellentesque nec nam aliquam sem et tortor consequat. Fames ac turpis egestas integer. Adipiscing commodo elit at imperdiet dui. Eget duis at tellus at urna condimentum. Massa sed elementum tempus egestas. Hendrerit dolor magna eget est lorem ipsum dolor. Penatibus et magnis dis parturient montes nascetur ridiculus.
Tortor pretium viverra suspendisse potenti. Varius duis at consectetur lorem donec massa sapien. Pellentesque elit eget gravida cum sociis. Amet cursus sit amet dictum sit amet justo. Mi eget mauris pharetra et ultrices. Pulvinar etiam non quam lacus suspendisse faucibus. Arcu dui vivamus arcu felis bibendum ut tristique et egestas. Sit amet luctus venenatis lectus magna fringilla urna. Nascetur ridiculus mus mauris vitae ultricies leo. Quis auctor elit sed vulputate mi sit amet mauris.
Vel pretium lectus quam id leo. Sodales ut eu sem integer vitae justo. Tincidunt eget nullam non nisi. Odio euismod lacinia at quis risus sed vulputate odio ut. Maecenas ultricies mi eget mauris pharetra et. Sit amet aliquam id diam maecenas ultricies mi eget mauris. Cursus turpis massa tincidunt dui ut ornare lectus. At tellus at urna condimentum. Amet cursus sit amet dictum sit amet. Habitant morbi tristique senectus et netus et malesuada.
Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Senectus et netus et malesuada fames ac turpis egestas. Vivamus arcu felis bibendum ut tristique et egestas. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Viverra tellus in hac habitasse platea dictumst vestibulum. Sollicitudin nibh sit amet commodo nulla facilisi nullam. Quis enim lobortis scelerisque fermentum dui. Odio morbi quis commodo odio aenean. Fermentum iaculis eu non diam phasellus. Lacus laoreet non curabitur gravida arcu ac tortor dignissim. Viverra maecenas accumsan lacus vel facilisis volutpat. Sem viverra aliquet eget sit amet.
p6h7ne94d59zjb4gq9sob7ivc2whoqi
737313
737312
2026-04-09T08:54:18Z
PieWriter
72123
Restored revision 737233 by [[Special:Contributions/AutoModeratorTest|AutoModeratorTest]] ([[User talk:AutoModeratorTest|talk]]) (TwinkleGlobal)
737313
wikitext
text/x-wiki
'''This is an example of an article.'''
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eleifend donec pretium vulputate sapien nec sagittis. Cras pulvinar mattis nunc sed blandit libero. Amet nulla facilisi morbi tempus. Vitae ultricies leo integer malesuada nunc vel. Eget est lorem ipsum dolor sit amet consectetur adipiscing. Vitae congue eu consequat ac felis donec et odio pellentesque. Pellentesque sit amet porttitor eget dolor morbi non. Amet luctus venenatis lectus magna fringilla. Libero volutpat sed cras ornare arcu dui vivamus. Quam id leo in vitae turpis massa sed elementum. Libero justo laoreet sit amet cursus. Diam sit amet nisl suscipit adipiscing bibendum est. Habitant morbi tristique senectus et. Tempus iaculis urna id volutpat lacus.
Condimentum mattis pellentesque id nibh tortor id. Odio euismod lacinia at quis risus. Elit sed vulputate mi sit amet mauris commodo quis. Aliquet risus feugiat in ante metus dictum at tempor commodo. Urna nunc id cursus metus aliquam eleifend. Commodo odio aenean sed adipiscing diam. Scelerisque in dictum non consectetur a erat nam at lectus. Mattis pellentesque id nibh tortor. Duis convallis convallis tellus id interdum velit laoreet id donec. Vitae tempus quam pellentesque nec. Scelerisque mauris pellentesque pulvinar pellentesque habitant. At tellus at urna condimentum mattis pellentesque id nibh. Ut porttitor leo a diam sollicitudin tempor id eu. Vel orci porta non pulvinar neque laoreet suspendisse. Sem fringilla ut morbi tincidunt augue interdum velit euismod.
Mauris pharetra et ultrices neque ornare. Justo laoreet sit amet cursus sit amet dictum sit amet. Id semper risus in hendrerit gravida rutrum quisque non. Vulputate dignissim suspendisse in est ante in nibh. Ut venenatis tellus in metus. Tellus molestie nunc non blandit. Vulputate enim nulla aliquet porttitor lacus luctus accumsan tortor. In hac habitasse platea dictumst vestibulum rhoncus est pellentesque elit. Donec adipiscing tristique risus nec feugiat in fermentum. Amet venenatis urna cursus eget nunc scelerisque viverra mauris in. Rhoncus est pellentesque elit ullamcorper dignissim cras.
Nisl condimentum id venenatis a condimentum vitae sapien. Amet nulla facilisi morbi tempus. Aliquam etiam erat velit scelerisque in dictum non. Libero volutpat sed cras ornare arcu. Sed viverra ipsum nunc aliquet. In iaculis nunc sed augue lacus viverra. Non arcu risus quis varius quam. Id ornare arcu odio ut. Egestas purus viverra accumsan in nisl nisi scelerisque. Massa id neque aliquam vestibulum morbi. Ac felis donec et odio pellentesque. Condimentum id venenatis a condimentum vitae sapien pellentesque. Magna eget est lorem ipsum. Vivamus arcu felis bibendum ut tristique et egestas quis. Consequat id porta nibh venenatis cras sed felis. Et tortor at risus viverra adipiscing at in tellus integer.
Ultrices vitae auctor eu augue ut lectus. Amet cursus sit amet dictum sit amet justo donec. Fames ac turpis egestas sed tempus urna. Vitae et leo duis ut diam. Massa id neque aliquam vestibulum morbi blandit cursus. Enim ut sem viverra aliquet eget. In hac habitasse platea dictumst vestibulum rhoncus est pellentesque. Auctor urna nunc id cursus. Id neque aliquam vestibulum morbi blandit cursus risus at. Mauris cursus mattis molestie a iaculis at.
Tortor aliquam nulla facilisi cras fermentum. Turpis egestas maecenas pharetra convallis. Tellus in metus vulputate eu. Mattis molestie a iaculis at erat pellentesque. Mi quis hendrerit dolor magna eget est. Pulvinar etiam non quam lacus suspendisse faucibus interdum posuere lorem. Nisl purus in mollis nunc sed id semper risus. Mi bibendum neque egestas congue. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Sit amet porttitor eget dolor. Mi bibendum neque egestas congue quisque egestas diam.
Egestas pretium aenean pharetra magna ac. Ante metus dictum at tempor commodo ullamcorper. Enim sed faucibus turpis in eu mi bibendum neque egestas. Blandit cursus risus at ultrices. Sem nulla pharetra diam sit amet. Congue eu consequat ac felis donec et. Pellentesque nec nam aliquam sem et tortor consequat. Fames ac turpis egestas integer. Adipiscing commodo elit at imperdiet dui. Eget duis at tellus at urna condimentum. Massa sed elementum tempus egestas. Hendrerit dolor magna eget est lorem ipsum dolor. Penatibus et magnis dis parturient montes nascetur ridiculus.
Tortor pretium viverra suspendisse potenti. Varius duis at consectetur lorem donec massa sapien. Pellentesque elit eget gravida cum sociis. Amet cursus sit amet dictum sit amet justo. Mi eget mauris pharetra et ultrices. Pulvinar etiam non quam lacus suspendisse faucibus. Arcu dui vivamus arcu felis bibendum ut tristique et egestas. Sit amet luctus venenatis lectus magna fringilla urna. Nascetur ridiculus mus mauris vitae ultricies leo. Quis auctor elit sed vulputate mi sit amet mauris.
Vel pretium lectus quam id leo. Sodales ut eu sem integer vitae justo. Tincidunt eget nullam non nisi. Odio euismod lacinia at quis risus sed vulputate odio ut. Maecenas ultricies mi eget mauris pharetra et. Sit amet aliquam id diam maecenas ultricies mi eget mauris. Cursus turpis massa tincidunt dui ut ornare lectus. At tellus at urna condimentum. Amet cursus sit amet dictum sit amet. Habitant morbi tristique senectus et netus et malesuada.
Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Senectus et netus et malesuada fames ac turpis egestas. Vivamus arcu felis bibendum ut tristique et egestas. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Viverra tellus in hac habitasse platea dictumst vestibulum. Sollicitudin nibh sit amet commodo nulla facilisi nullam. Quis enim lobortis scelerisque fermentum dui. Odio morbi quis commodo odio aenean. Fermentum iaculis eu non diam phasellus. Lacus laoreet non curabitur gravida arcu ac tortor dignissim. Viverra maecenas accumsan lacus vel facilisis volutpat. Sem viverra aliquet eget sit amet.
eiib1n2qqzim48t3mbjl9fe5j76s6y5
Jayakumar Jitendrasinh Rawal
0
106623
737220
689395
2026-04-08T18:10:58Z
Bad-editor-111
62134
AutoModerator Test
737220
wikitext
text/x-wiki
{{DEFAULTSORT:Rawal, Jayakumar Jitendrasinh}}
[[Category:Members of the Maharashtra Legislative Assembly]]
[[Category:Maharashtra MLAs 2009–2014]]
[[Category:Maharashtra MLAs 2014–2019]]
[[Category:People from Dhule]]
[[Category:Bharatiya Janata Party politicians from Maharashtra]]
[[Category:Living people]]
[[Category:1975 births]]
[[Category:Maharashtra MLAs 2019–]]
ozaztxbw5iej0szde0mbi75spi5cifc
UNIMARC Bibliographic Format
0
108557
737235
690650
2026-04-08T18:26:25Z
Reallylongstringksdjuhaifuhwefwjshfsefkhbfkbwhefkbwhebdwkshbdsjfh
72932
737235
wikitext
text/x-wiki
= INTRODUCTION =
== Purpose and Scope of UNIMARC ==
The primary purpose of UNIMARC is to facilitate the international exchange of bibliographic data in machine-readable form between national bibliographic agencies. UNIMARC may also be used as a model for the development of new machine-readable bibliographic formats.
...
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
= ORGANIZATION OF THE MANUAL =
== General Organization ==
Section 1, INTRODUCTION, and Section 2, ORGANIZATION OF THE MANUAL, give introductory material to assist in the use of this Manual. ...
= FORMAT STRUCTURE =
== General Structure ==
UNIMARC is a specific implementation of ISO 2709, an [[international standard]] that specifies the structure of records containing bibliographic data. ...
= RECORD LABEL AND DATA FIELDS — GENERAL INTRODUCTION =
== Repetition of Fields and Subfields ==
Fields and subfields within a record may be repeated in line with the instructions given under Occurrence in each data field description and under the definitions of the subfields. ...
= RECORD LABEL AND DATA FIELDS – FIELD DESCRIPTION =
Field list<br>
Each field listed on the following pages is explained following the eight-item outline described in Section 2.1. ...
= RECORD LABEL =
Definition<br>
This area of the record contains general information which may be needed in processing the record, constituted according to the provisions of ISO 2709. ...
= DATA FIELDS =
== 0-- IDENTIFICATION BLOCK ==
== 1-- CODED INFORMATION BLOCK ==
== 2-- DESCRIPTIVE INFORMATION BLOCK ==
== 3-- NOTES BLOCK ==
== 4-- LINKING ENTRY BLOCK ==
== 5-- RELATED TITLE BLOCK ==
== 6-- SUBJECT ANALYSIS AND BIBLIOGRAPHICAL HISTORY BLOCK ==
Definition and Scope of Fields<br>
This block contains subject data constructed according to various systems, both verbal and notational. It also contains data relating to the physical characteristics of the item and the mode of presentation of the matter in it (608), access points for the publication, performance, etc. of the item (620) and later history of the copy in hand (621). The following fields are defined:
:Subject headings
:600 Personal Name Used as Subject
:601 Corporate Body Name Used as Subject
:602 Family Name Used as Subject
:604 Name and Title Used as Subject
:605 Title Used as Subject
:[[UNIMARC_Bib_606:_Topical_name_used_as_subject|606 Topical Name Used as Subject]]
:607 Geographical Name Used as Subject
:608 Form, Genre or Physical Characteristics Heading
:610 Uncontrolled Subject Terms
:615 Subject Category (Provisional)
:616 Trademark Used as Subject
:Bibliographical history
:620 Place and Date of Publication, Performance, etc.
:621 Place and Date of Provenance
:626 Technical Details Access (Electronic Resources) (Obsolete)
:Subject analysis
:660 Geographic [[North American Numbering Plan|Area Code]] (GAC)
:661 Time Period Code
:670 PRECIS
:Classification
:675 Universal Decimal Classification (UDC)
:676 Dewey Decimal Classification (DDC)
:680 Library of Congress Classification
:686 Other Class Numbers
Occurrence<br>
Fields in the 6-- block are all optional. It is recommended that all subject data in a source record suitable for international exchange be entered in the UNIMARC record.
Notes on Field Contents
Punctuation should be entered as in the source format.
== 7-- RESPONSIBILITY BLOCK ==
== 8-- INTERNATIONAL USE BLOCK ==
== 9-- NATIONAL USE BLOCK ==
= APPENDIXES =
== Appendix A: Language Codes ==
[[UNIMARC:_Language_Codes|UNIMARC: Language Codes]]
== Appendix B: Country Codes ==
[[UNIMARC:_Country_Codes|UNIMARC: Country Codes]]
== Appendix C: Relator Codes ==
== Appendix D: Geographic Area Codes ==
== Appendix E: Time Period Code ==
== Appendix F: Cartographic Materials Codes ==
== Appendix G: Subject Systems Codes ==
== Appendix H: Cataloguing Rules and Formats Codes ==
== Appendix I: Table of Values ==
== Appendix J: Character Sets ==
== Appendix K: Documentation to Accompany Exchange Records ==
== Appendix L: Complete Examples ==
== Appendix M: Bibliography ==
== Appendix N: Useful Addresses ==
== Appendix O: Format Changes ==
49odoxngicws9nrhctf32ejam0qm3i7
Dušníky nad Vltavou
0
108748
737221
581516
2026-04-08T18:11:54Z
Bad-editor-111
62134
AutoModerator Test
737221
wikitext
text/x-wiki
Kronika obce Hostín zmiňuje ještě povodeň v roce 1772, po které následovala nouze a hlad, a povodeň roku 1900 (kolem 9. 4.). K větší povodni, kterou dušnický kronikář v přehledu opominul, patří i povodeň roku 1940, která v Dušníkách zaplavila cesty a pole i chmelnice, do vsi se však nedostala. Podle dostupných údajů se vůbec nejničivějšími povodněmi (pro Prahu a pravděpodobně i pro celé dolní Povltaví) jeví povodně z roku 1432 a 1784<ref>{{Citace monografie
| příjmení = Státníková
| jméno = Pavla
| příjmení2 =
| jméno2 =
| titul = Zmizelá Praha. Povodně a záplavy.
| vydání =
| vydavatel = Paseka
| místo =
| rok = 2012
| počet stran =
| strany =
| isbn =
}}</ref>.
=== Ochrana proti povodním ===
Jakkoli to není při zběžném pohledu patrné, Dušníky leží na nevysokém ostrohu. Při menších povodních voda sice překonala hráze a dostala se do polí, chmelnic a zahrad, ale neohrozila obydlí. V případě stoletých vod obyvatelé zpravidla nacházeli útočiště ve statku čp. 10, nejvýše položeném. Jako částečná ochrana před povodněmi sloužily snad i tarasy (v katastru je doloženo pomístní jméno Na tarase, Na Taradii), nasucho stavěné zídky střídané hliněnými náspy. Protipovodňová opatření zesilovala od 18. století. Od poloviny 18. století se věnovali stavbě dřevěných vzpěr břehů také majitelé panství Chotkové, vzpěry byly nazývány jako sruby (dokladem je snad pomístní jméno Na zdrubech) a kobyly.
V roce 1785 tak vznikl také plán regulace Vltavy na zdejším panství od Františka Hegra, který zahrnuje regulaci toku, prorážení poloostrovů i budování hrází. Po roce 1816 docházelo k dalším průkopům a regulacím na dolní Vltavě, v dolním Povltaví pod taktovkou navigačního ředitele Gerstnera. Další úpravy a opravu dušnické hráze si vyžádala povodeň z roku 1862 (práce proběhly o sedm let později) a pak také následující z roku 1890 (opravy v roce 1896).
Po vybudování tzv. Vltavské kaskády (1930–1959) se dařilo po desetiletí tok Vltavy regulovat, a tak další „stoletá“ povodeň následovala po události 1890 se zpožděním, až v roce 2002. Přinesla však po moravských povodních z devadesátých let 20. století další „civilizační šok“. V roce 2013, patrně i díky lepší připravenosti vodohospodářů, neměla povodeň tak ničivé následky a byla hodnocena jako voda dvacetiletá nebo padesátiletá. Přesto povodeň téměř zničila nový dálniční most vystavený nad silnicí spojující Všestudy s Dušníky, který byl postaven po povodni z roku 2002. Popovodňová opatření zahrnovala především opravy protržených hrází a čištění (prohlubování koryta), ke znovuzakládání mokřadů v dolním Povltaví nedochází, nedaří se ani stavba vyšší hráze u obce Zálezlice (patřila roku 2002 a 2013 k nejvíce postiženým), zejména kvůli vlastnickým sporům.
=== Proměny řečiště Vltavy v dolním Povltaví ===
Vltava měnila koryto i v posledních staletích. V letech 1667–1683 například změnila řeka řečiště od Zelčína k Doničkám, pobořila kostel v Doničkách a roku 1798 byly z něho již jen zříceniny, přitom ves zanikla zcela. Při povodních roku 1700 de facto zanikla část blízké vsi Ouholice. Nejvýznamnější změnou pro Dušníky byl zánik „pravého“ vltavského ramene, které fungovalo v té době jako rameno hlavní, odbočovalo ve Veltrusích a vracelo se zčásti v oblasti Kubantova, vytvářeje ostrov. Rameno zčásti pokračovalo přes Vojkovice, patrně vedeno ostrohem zvaným Hebron. V 80. letech 18. století už muselo být původně hlavní rameno ve veltruském parku uměle napájeno vodou z řeky, aby si park uchoval kouzlo vodou protkané krajiny. Při pozdějších povodních se řeka vracela do starých řečišť a poškozovala stavby a hráze po svém původním toku, tedy i na dušnickém "Kubantově". Tůně v okolí vsi měnily tvar, vznikaly a zanikaly, některé z nich byly později zasypány. Například na louce při Vltavě od dušnického přívoznického domku výš proti proudu se nacházely až do roku 1904 tůňky. Při provádění kanalizace Vltavy byly zasypány výkopem materiálu z jezu u Vraňan.
=== Záznamy o povodních v Dušníkách ===
==== 18. století ====
Ve starších historických záznamech se dochovaly zápisy o povodních v Dušníkách jen sporadicky.
V roce 1752 hlásí vrchnosti velkou škodu na polích dušnický rychtář Matěj Pokorný s tím, že mu zbylo k užitku po velké povodni jen 45 ze 100 strychů polí.
Farní pamětní kniha v Lužci nad Vltavou připomíná velkou povodeň roku 1768, kdy se rozlila voda, když se nastavil led v Lužci, po celém okolí: Bukol, Hostín, Vojkovice, Křivousy, Jedibaby, Dušníky nad Vltavou byly zatopeny.
[[Soubor:Ledové kry ve Veltrusích.jpg|náhled|Ledové kry ve Veltrusích ]]
Podobné události se připomínají roku 1769, 1771 a 1773, kdy opět celá krajina až do Ouholic a Hostíně byla zatopena.
Dobře jsou dochovány zprávy o výjimečných povodních let 1784 a 1785. Lidový kronikář Pražák z Ouholic si mj. zapsal: „Roku 1783 začala počátkem prosince krutá zima a trvala nepřetržitě až do 15. února 1784. Led na Vltavě dosáhl 3/4 lokte síly. Velmi často hřmělo a blýskalo se, načež 27. února hnul se náhle led, způsobil povodeň, takže mnohé pobřežní dědiny pohubil. … V roce 1785 byla velkou vodou protržena dušnická hráz u Všestud.“ Při těchto povodních (zejména té první, ledové a smíšené) zaniklo v dolním Povltaví hned několik vesnic a řeka částečně změnila průběh toku (koryta). Ostatně povodeň roku 1784 pustošila přírodu a sídla v mnoha povodích v celé Evropě (Dunaj, Odra, Labe, Rýn, Seina, Máza, Loira) a měla na svědomí stržení několika pilířů pražského Karlova mostu.
„Roku 1799“ píše ouholický kronikář Hodek, „byla veliká povodeň s ledem, který zase náramně silný led s hřmotem dne 23. fevruára se strhnul a voda okolo pivováru šla a všady ledy s sebou nesla. U nás u sloupu zvonečkového, který stál proti č. 13 na 1/4 lokte vystoupila a opět v zahradách a gruntech dosti škody způsobila, protože když opadla a zem roztála, zase silné mrazy přešly, zkrze které mnoho stromů, zvlášť švestkových v kořenech nejlepším užitkem zmrzlo a poschlo. Příčinou tak časté velké vody jest, že je od ostrovské (veltruské) strany všechna na naši stranu sehnána a sevřena ledy.“
==== 19. století ====
Kronikáři také popisují ledové zácpy, které se opakovaly ve 40. letech 19. století od ledna do března a nesly velké nebezpečí: obrovské kusy ledu strhávaly břehy, pak znovu zamrzaly, tály a takto ztuhlé obrovské kry ohrožovaly celé okolí řek. Lidé opětovně zamrzlý povrch místy prosekávali a uvolňovali, aby se náplava ledu (obvykle se hromadící v ohybech řeky, tedy právě u Dušník) mohla volně odtéct.
[[Soubor:05-04-29-kronika-povoden1890.jpg|vlevo|náhled|Po povodni roku 1890 u dušnických tůní|alt=foto vlastní rodinný archiv]]
Ke smíšené a ledové povodni roku 1845 uvádí hostínský kronikář Vorlíček: „A co vesnic je vod Prahy až pod Mělník, a kde ještě za Mělníkem až k Litoměřicům, pořád ta voda je zatopila. Tak se rozmohla až do Chvatěrub, šla starými řečišťaty až do Veltrus a tam stavení pobořila a odtud se rozlila až pod Zlosyň, až do Vojkovic, takže Vojkovice ze všech stran vodou byly obklíčeny. U Dušník byly všechny hráze ztrhaný a pole strhaný a některý pískem aneb šutrem zanešený, takže sedláci mnoho set za to platit musili, aby jim to lidé vyvezli.“ Ouholický sedlák Pražák k této velké vodě kromě jiného uvedl: „Kubantov se podobal pískové poušti, neboť protržením dušnické hráze tam dostala voda hrozně prudký běh a tím tolik písku tam nanesla, že nebylo ani pomyšlení poznati meze neb hranice svého pozemku, tím méně snad chmelné řádky neb nějaké zaseté obilí… Po této povodni byly konané v celém mocnářství sbírky pro obce povodní poškozené. Do naší obce se však málo dostalo, bezpochyby proto, že jsme nevěděli u koho se včas hlásit. V Dušníkách byli již v této věci šťastnější, neboť vlastník č. 10 v Dušníkách dostal almužnu 1800 zlatých.“
Při únorové povodni roku 1862, která byla způsobena silnými dešti a rychlou oblevou, u Kralup vzala voda 30 [[sáh]]ů kolejí a trať musela být přeložena. Taktéž o deset let později, roku 1872, povodeň ničila a opětovně vytvořila jezero od Dušník k soutoku Vltavy s Labem. Zimní povodně napáchaly škody v polích zejména tím, že přinášely množství materiálu, písku a štěrku, a tím pole znehodnocovaly, naplavené dříví také ničilo konstrukce chmelnic a stromy.
Povodeň z roku 1872 měla charakter povodně bleskové, která přichází bez předchozího očekávání (ne po dlouhé a na sníh bohaté zimě), zpravidla vinou přívalových srážek.Nejvíce zasáhla povodí Berounky, kde způsobila stovky (!) ztrát na životech. Její síla v dolním Povltaví nebyla tak ničivá.
Větší následky pro obyvatele měly obecně právě povodně jarní a letní, při kterých byla znehodnocena veškerá úroda, jako v roce 1890, kdy jezero počínající u Dušník dosahovalo až k Neratovicím. Podle pamětnice Rosalie Králové za povodně roku 1890 voda stoupla tak, že zatopila i stavení přívozníka č. 6, takže musel odvést kozy do statku č. 10 na dvůr, kde byl už shromážděn dobytek z celé vsi. Pro chléb se tehdy jezdilo do Vojkovic na loďce. Povodeň se stala velmi "mediálně známou" hlavně vinou fotografie znovu poničeného pražského Karlova mostu, kterou otiskly nejen české noviny.
Povodeň se pak opakovala v menším rozsahu roku 1897 a 1899, kdy 15. září nešťastným pádem při vytahování stavidel za povodně zemřel tehdy šedesátiletý Antonín Bittner, poklasný z Dušník.
==== 20. století ====
Jediné dvě velké dušnické povodně 20. století líčí kronika obce Dušníky:
„10. března 1940 se Vltava též rozvodnila. Toho dne se hnuly ledy, které byly na Vltavě, zejména u Štěchovic, nakupeny. V Dušníkách chybělo jen několik decimetrů, aby se voda přelila přes ochrannou hráz. Zpětná voda Vltavy zaplavila chmelnice mezi řekou a vsí, cesta od přívozu do vsi byla u Herbstovy zahrady pod vodou. Zpětná voda zatopila i obecní silnici Dušníky-Všestudy „v topolách“ a tekla přes chmelnice u silnice, nynější višňovku Fruty k Vojkovicím. Během povodně dušničtí občané byli v pohotovosti a hráz u „společné tůně“ zesilovali pytli písku. Po povodni byla cesta k přívozu opravena, zvýšena, zejména u Herbstovy zahrady a uválcována okresním parním válcem.
V roce 1954 po suchém červnu začalo prvý týden v červenci vydatně pršet. Ve Vltavě začala voda stoupat, vylila se z břehů a dne 10. července dostoupila největší výše, a to asi 1,30 m pod korunu hráze u přívozníkova domku. Opadávání velké vody trvalo týden.“ Povodeň roku 1954 naštěstí zmírnilo rozestavěné vodní dílo Slapy, které značně snížilo kulminaci. Mýtus o kaskádě, která zabrání všem povodním, byl na světě.
==== 21. století ====
[[Povodeň v Česku (2002)|Povodně roku 2002]], které ostatně postihly i další středoevropské země, byly způsobeny dvěma vlnami tlakové níže s nimi spojenými frontálními systémy. Vydatné deště trvaly ve dnech 6.–7. srpna, a to převážně v jižních Čechách, a poté 11.–13. srpna, kdy zasáhly celé povodí Vltavy včetně přítoků Sázavy a Berounky. Obě tlakové níže zasáhly naše území nejdeštivějším sektorem a nadto postupovaly pomalu. Nádrže vltavské kaskády nasycené první vlnou vody z Vltavy a přítoků, kterou dokázaly zvládnout, neodolaly druhé vlně. Vltavská kaskáda přestala vodu zadržovat. Vltava v Praze kulminovala 13. srpna s 782 cm při průtoku 5160 m<sup>3</sup>/s, což odpovídá opakování jednou za 500 let. Poté stupňovitě klesala v souvislosti se snižováním odtoku vltavské [http://www.pvl.cz/files/download/hydrologicke-informace/zpravy-o-povodni/2002-08-zprava-o-povodni.pdf kaskády] {{Webarchive|url=https://web.archive.org/web/20140626101836/http://www.pvl.cz/files/download/hydrologicke-informace/zpravy-o-povodni/2002-08-zprava-o-povodni.pdf |date=2014-06-26 }}.
[[Soubor:Protržená povodňová hráz u Dušník - srpen 2002.jpg|vlevo|náhled|Protržená povodňová hráz u Dušník - srpen 2002]]
Teprve po pražské kulminaci dopadly na naši obec nejtěžší následky povodně. Pražské Staré Město zachránily protipovodňové stěny, dále na sever se Vltava rozlévala do šíře, kdekoli to terén umožnil, tedy i v oblasti severně od Kralup po Mělník.
[[Soubor:Povodeň 2013 Dušníky nad Vltavou.jpg|náhled|Letecký snímek Dušník nad Vltavou při povodni 2013]]
V Dušníkách a okolí byla hráz na několika místech protržena. U Všestud se voda prodrala proudem pod dálničním mostem (dálnice D8), který poškodila tak, že byl na čas zastaven, posléze omezen provoz dálnice. Most byl provizorně opraven a poté zásadním způsobem zpevněn. Veškerá pole a chmelnice byly pod vodou. V Dušníkách byly zaplaveny všechny domy s výjimkou čp. 10 a čp. 11. Veškeré obyvatelstvo bylo evakuováno a týden mohlo sledovat svou ves z protějšího vyvýšeného břehu u Mlčechvost. Při odklízení povodňových škod pomáhali kromě armádních a bezpečnostních složek také dobrovolníci.
Tlaková níže a vydatné srážky včetně přívalových dešťů byly na vině další z povodní, která proběhla povodím Vltavy počátkem června '''2013'''. Květen byl srážkově nadprůměrný a zmíněná vlna dešťů zvýšila už tak zvýšené hladiny toků. Srážky byly v jižních a středních Čechách vydatné a prudké, ke zhoršení situace přispěly drobnější taky a neměřitelné mezipovodí (stráně svažující se přímo do toků), které hrají obvykle při povodních okrajovou roli.
Voda byla v prvních dnech června zadržována vltavskou kaskádou, aby bylo možné připravit hlavní město na největší nápor. 2. června došlo k evakuaci Dušník (odmítli pouze obyvatelé domu čp. [https://www.flickr.com/photos/yancad/sets/72157634030488853/ 12]) a 3. června kulminovala voda v Praze a poté i ve Vraňanech na 785 cm / 3140 m<sup>3</sup> (11:40)[http://www.vsestudy.cz/2013/06/vody-vic-nez-v-roce-2006-a-stale-pribyva/].
Starostové Kralup a Mělníka žádali opakovaně po zajištění Prahy snížení průtoků vltavské kaskády, aby silně zvýšený odtok nepostihl jejich města tak drtivě. Na žádost starosty Kralup nad Vltavou Petra Holečka nakonec ministr zemědělství Petr Bendl rozhodl o snížení odtoku o 400 m<sup>3</sup>/s, čímž mj. pravděpodobně zabránil i zaplavení přízemí více domů v Dušníkách a dalším škodám. V Dušníkách bylo částečně zatopeno přízemí dvou domů, zahrady a [https://www.facebook.com/Dusniky/ sklepy]. Voda se opět dostala do vesnice od Dědibab a dále průrvou pod dálničním mostem u Všestud. Most, zpevněný v roce 2002, musel být včetně náspů opět opraven v následujících měsících. Voda totiž protrhla násep u Všestud, který nebyl zpevněn, a následný silný proud vymlel násep pod vozovkou u jedné části mostu tak, že odkryl až základy. Nakonec byly základy zpevněny a doplněny a násep u Všestud vybudován zcela nový s využitím železných [https://www.flickr.com/photos/yancad/sets/72157642751617153 prvků]. [https://www.rsd.cz/wps/portal/web/rsd/vyhledavani/!ut/p/a1/jZHNToNAFIWfxQXb3kvLn-5g0Bap2KhNYTYNhUlLAkyFAezj-DC-l9PGaNSUMrtJvi_nzBmgEAIt4zbbxiLjZZwf79RY-57vqQ5BfzpBFW3DvZ3o8xdEdSyBSAJ45tj420f0TLQ1x9UXUzJBC7_8H8AhRAJ3OmqLAB9tbVg-mdozzZzLRM0ao-c6M9e8fpBpxjC_p-AFfwX0hPQ1OAF9E_0Z6d8G90C3Od-c_iPaCbG_UVDBrutG2aYYJbxQMOeiqRWsWVwlO6A5T-KcQcjKwcprw6oDhO3HO6tFkx4gktOYZ98-M2DVZqyD52P_mjdVwggvBStFwFO2fmJlyqqL60o14Xvmpd8CRA7QKuI02MK-WC6XIWaLYmUJ6y3QW-_qE5MtXsU!/?urile=wcm:path%3A%2FPortal%2BSite%2FZ6_000000000000000000000000A0%2FZ6_CGAH47L0004820IDBHD79M00I6%2FZ6_KIKI1BC0K0BCC0A4F504PN0OA4%2FZ6_KIKI1BC0K83A70A47PA5RD1086%2F89e3e893-b723-4736-acc8-70c8464159ae] {{Webarchive|url=https://web.archive.org/web/20200607035715/https://www.rsd.cz/wps/portal/web/rsd/vyhledavani/%21ut/p/a1/jZHNToNAFIWfxQXb3kvLn-5g0Bap2KhNYTYNhUlLAkyFAezj-DC-l9PGaNSUMrtJvi_nzBmgEAIt4zbbxiLjZZwf79RY-57vqQ5BfzpBFW3DvZ3o8xdEdSyBSAJ45tj420f0TLQ1x9UXUzJBC7_8H8AhRAJ3OmqLAB9tbVg-mdozzZzLRM0ao-c6M9e8fpBpxjC_p-AFfwX0hPQ1OAF9E_0Z6d8G90C3Od-c_iPaCbG_UVDBrutG2aYYJbxQMOeiqRWsWVwlO6A5T-KcQcjKwcprw6oDhO3HO6tFkx4gktOYZ98-M2DVZqyD52P_mjdVwggvBStFwFO2fmJlyqqL60o14Xvmpd8CRA7QKuI02MK-WC6XIWaLYmUJ6y3QW-_qE5MtXsU%21/?urile=wcm%3Apath%3A%2FPortal%2BSite%2FZ6_000000000000000000000000A0%2FZ6_CGAH47L0004820IDBHD79M00I6%2FZ6_KIKI1BC0K0BCC0A4F504PN0OA4%2FZ6_KIKI1BC0K83A70A47PA5RD1086%2F89e3e893-b723-4736-acc8-70c8464159ae |date=2020-06-07 }}
== Doprava ==
Obcí prochází silnice III/10151.
Dušníky leží asi 7 km od exitu „Uhy“ – dálnice [http://www.ceskedalnice.cz/dalnice/d8/ D8] – jsou tedy dopravně dobře dostupné. Hromadnou dopravu obstarávají autobusy PID č. 473 – v pracovní dny celkem 7 spojů směrem na Kralupy nad Vltavou, o víkendu bez autobusového spojení. Poříční přívoz do vsi Vepřek zanikl po roce 1957.
== Turistika ==
Ves se nachází na rušné mezinárodní cyklostezce Praha – Drážďany.
Dušníky jsou přístupné pro pěší nebo cyklisty z veltruského parku (asi 3 km od Červeného mlýna) přes tzv. Kubantov, kdysi překrásnou oblast mezi Mlýnským potokem a Vltavou. Na Kubantově najdeme zbytky lužních porostů, luk, ovocných sadů a vltavských tůní (tzv. rybníčků, které sahají po Dušníky). Pěší turista musí ovšem překonat (podejít) hlučný dálniční most, který oblast znehodnocuje.
Na druhou stranu od Dušníky podél Vltavy lze dojít po hrázi nebo pobřežní polní cestě až k vraňansko-dědibabskému jezu a vodní elektrárně. V ohybu hráze najde pozorný chodec trosky nejzazšího pavilonu veltruského [http://www.vsestudy.cz/2006/02/tajemna-stavba-chotkovsky-altanek/ parku].
Okolí rybníčků a vltavské pobřeží je oblíbeným místem rybářů, konají se zde příležitostně i rybářské závody.
Ve statku čp. 10 byla zřízena v roce 2017 restaurace a penzion.
== Zajímavosti ==
Ve vesnici mají stálou adresu dva spolky, [http://www.dolnipovltavi.cz/ Dolní Povltaví] a [http://havranek.dolnipovltavi.cz/ Rodinné centrum Havránek], na hranici katastru Dušník a Všestud sídlí motorkářské sdružení [https://www.feistypistons.cz Feisty Pistons].
Od roku 2009 Dušníky každoročně na přelomu července a srpna probíhá běžci oblíbený [http://www.mkmirejovice.cz Miřejovický půlmaraton].
V roce 2001 byl zaznamenán případ tornáda na území [http://www.tornada-cz.cz/pripady/vsestudy-okr-melnik:a170.htm Dušník] (poblíž sušárny chmele u dálnice D8). Podle pamětníků se v těchto místech podobné úkazy dějí opakovaně.
[[Soubor:Znak-dušník.png|náhled|165x165pixelů|Neoficiální znak Dušník nad Vltavou od Václava Landsingera]]
V roce 2016 vytvořil Václav Landsinger z Dušník neoficiální znak osady Dušníky, který odkazuje na obecní znak všestudský a připomíná přítomnost Vltavy, chmelařskou tradici i pamětihodnost - zvoničku na návsi.
Ve školní kronice ve Zlosyni je zanesena pověst o vyhubení Vršovců. Podle vypravování Fr. Homolky, rolníka ve Zlosyni, se tradovalo, že u dušnických lesíků byl zavražděn poslední Vršovec. Zejména dušnické děti věřily, že hlubokou Černou tůň za Dušníky hlídá černý pes.
V 19. století a ještě na počátku 20. století byl poštovní úřad pro Dušníky umístěn v Jenišovicích. Transport pošty ale znemožňovala často rozvodněná Vltava a zdržoval přívoz, Dušničtí proto opakovaně žádali o změnu poštovního úřadu, ke které však došlo až po vzniku Československé republiky (dodnes je poštovním úřadem pošta Vojkovice, ačkoliv pro nadřazenou obec Všestudy platí poštovní úřad Veltrusy).
== Odkazy ==
=== Reference ===
<references />
=== Externí odkazy ===
* {{commonscat}}
* [http://dusniky.vsestudy.cz webové stránky]
* [https://www.facebook.com/Dusniky stránka na Facebooku]
{{Pahýl}}
{{Části české obce}}
{{Autoritní data}}
{{Portály|Česko|Geografie}}
[[Kategorie:Všestudy (okres Mělník)]]
[[Kategorie:Vesnice v okrese Mělník]]
[[Kategorie:Svazek obcí Dolní Povltaví]]
[[Kategorie:Sídla ve Středolabské tabuli]]
[[Kategorie:Sídla na Vltavě]]
qcqe1p64myno05q0lr127jj5uqslmzy
Tim Flannery
0
113678
737287
690586
2026-04-09T00:04:05Z
InternetArchiveBot
34092
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.5
737287
wikitext
text/x-wiki
{{for|the baseball player and coach|Tim Flannery (baseball)}}
{{Use Australian English|date=October 2016}}
{{Use dmy dates|date=March 2013}}
{{Infobox person
| name = Tim Flannery
| image = Tim Flannery.jpg
| image_size =
| alt =
| caption = Tim Flannery at the 5th World Conference of Science Journalists, 2007
| birth_name = Timothy Fridtjof Flannery
| birth_date = {{Birth date and age|1956|01|28|df=yes}}
| birth_place = [[Melbourne]], [[Victoria (Australia)|Victoria]]
| residence =
| nationality = [[Australian]]
| education =
| alma_mater = [[La Trobe University]]
| spouse =
| occupation =
| years_active =
| employer =
| organization = [[University of Melbourne]], [[Graduate Institute of International and Development Studies]]<ref>{{cite web| title=Tim Flannery's new institution: Graduate Institute| url=http://graduateinstitute.ch/directory/_/people/faculty/flannery| access-date=6 July 2020| archive-date=11 April 2019| archive-url=https://web.archive.org/web/20190411063030/http://graduateinstitute.ch/directory/_/people/faculty/flannery| url-status=dead}}</ref>
| known_for = Public speaking on the environment
| notable_works =
| awards = [[Australian of the Year]] <ref>{{cite web| title=Tim Flannery | type= re: Australian of the Year 2007| url= http://www.onlymelbourne.com.au/melbourne_details.php?id=10761| work=Only Melbourne| accessdate=23 May 2011}}</ref>
}}
'''Timothy Fridtjof Flannery''' {{post-nominals|country=AUS|FAA}} (born 28 January 1956) is an Australian [[Mammalogy|mammalogist]], [[paleontology|palaeontologist]], [[Environmentalism|environmentalist]], [[Conservation biology|conservationist]],<ref>{{cite web| title=Tim Flannery on Andrew Marr talk| url=https://www.bbc.co.uk/programmes/b00z58b0| work=BBC Radio-Start the Week| accessdate=23 May 2011}}</ref> [[Exploration|explorer]],<ref>{{cite web| title=Tim Flannery| url=https://australianmuseum.net.au/tim-flannery| accessdate=24 April 2018}}</ref> and public scientist. Having discovered more than 30 [[Mammal|mammal species]]<ref>{{cite web| title=Interview: Tim Flannery, National Geographic| url= https://www.youtube.com/watch?v=-sS_aUYAgEA}}</ref> (including new species of [[Tree-kangaroo|tree kangaroos]]<ref>{{cite web| title=Tree Kangaroo from New Guinea, Australian Museum| url= https://australianmuseum.net.au/mammology-collection-tree-kangaroo-from-new-guinea}}</ref>), he served as the Chief Commissioner of the [[Climate Commission]], a Federal Government body providing information on [[climate change]] to the Australian public. On 23 September 2013, Flannery announced that he would join other sacked commissioners to form the independent [[Climate Council]], that would be funded by the community.
Flannery is a professorial fellow at the Melbourne Sustainable Society Institute, [[University of Melbourne]].
Flannery was named Australian Humanist of the Year in 2005<ref>[https://vichumanist.org.au/australian-humanist-awards/ahoy-2000-2020/ Australian Humanist of the year awards 2000 to current.]</ref>, and [[Australian of the Year]] in 2007. Until mid-2013 he was a professor at [[Macquarie University]] and held the Panasonic Chair in Environmental Sustainability.<ref name="pana chair">Macquarie University (2013). "[http://web.science.mq.edu.au/for/business_and_community/panasonic-chair/ PanasonicChair] {{webarchive|url=https://web.archive.org/web/20130712122647/http://web.science.mq.edu.au/for/business_and_community/panasonic-chair/ |date=12 July 2013 }}". Retrieved 23 June 2013.</ref> He was also chairman of the [[Copenhagen Climate Council]], an international group of business and other leaders that coordinated a business response to climate change and assisted the Danish government in the lead up to COP 15.<ref name=copenhagen>Copenhagen Climate Council (2008). "[http://copenhagenclimatecouncil.com/index.php/tim-flannery-chairman Tim Flannery] {{Webarchive|url=https://archive.is/20080708220356/http://copenhagenclimatecouncil.com/index.php/tim-flannery-chairman |date=8 July 2008 }}". Retrieved 17 May 2008.</ref> In 2015, the Jack P. Blaney Award for Dialogue recognized Tim Flannery for using dialogue and authentic engagement to build global consensus for action around climate change.<ref name="sfu.ca">{{cite web|title=Climate Solutions with Tim Flannery|url=https://www.sfu.ca/dialogue/watch-read-discover/tim-flannery.html|accessdate=1 March 2019|archive-date=30 July 2020|archive-url=https://web.archive.org/web/20200730085707/https://www.sfu.ca/dialogue/watch-read-discover/tim-flannery.html|url-status=dead}}</ref> His sometimes controversial views on shutting down conventional coal-fired power stations for electricity generation in the medium term are frequently cited in the media.
== Background ==
Flannery was raised in a Catholic family in the Melbourne suburb of [[Sandringham, Victoria|Sandringham]], close to [[Port Phillip Bay]], where he learned to fish and scuba dive and became aware of marine pollution and its effects on living organisms.<ref>{{cite web |url=https://www.themonthly.com.au/issue/2015/november/1446296400/tim-flannery/power-place |title= The Power of Place|periodical=The Monthly|last1= Flannery |first1= Tim |date= November 2015|publisher= Black Inc |access-date= 30 December 2015}}</ref> He completed a Bachelor of Arts degree in English at [[La Trobe University]]<ref>[http://www.latrobe.edu.au/alumni/profiles/full-profile?mode=results&queries_given-name_query=Tim&queries_family-name_query=Flannery Alumni profile search result, La Trobe University] {{webarchive|url=https://web.archive.org/web/20120627122339/http://www.latrobe.edu.au/alumni/profiles/full-profile?mode=results&queries_given-name_query=Tim&queries_family-name_query=Flannery |date=27 June 2012 }}</ref> in 1977, and then took a change of direction to complete a Master of Science degree in [[Earth Science]] at [[Monash University]] in 1981.<ref>Dapin, M. (2014). [https://www.smh.com.au/national/tim-flannery-a-man-for-all-climates-20140207-3271c.html Tim Flannery: a man for all climates.] The Sydney Morning Herald. Available at: [Accessed 13 Mar. 2019].</ref> He then left Melbourne for Sydney, enjoying its subtropical climate and species diversity.<ref>Introduction, ''The Birth of Melbourne'', {{ISBN|1-877008-89-3}}</ref> In 1984, Flannery earned a doctorate at the [[University of New South Wales]] in Palaeontology for his work on the evolution of [[Macropodidae|macropod]]s (kangaroos).<ref>Hayes, D. (2019). [https://www.britannica.com/biography/Tim-Flannery Tim Flannery: Australian Zoologist], Encyclopedia Britannica. Available at: [Accessed 4 Mar. 2019].</ref>
Flannery has held various academic positions throughout his career. He spent many years in Adelaide, including a spell as professor at the [[University of Adelaide]], and 7 years as director of the [[South Australian Museum]]. He was also principal research scientist at the [[Australian Museum]], during which time he worked to save the bandicoot population on North Head. In 1999 he held the year-long visiting chair of [[Australian studies]] at [[Harvard University]].<ref name="The Weather Makers-About TF">{{cite web|url=http://www.theweathermakers.org/about |title=About Tim Flannery |publisher=The Weather Makers |accessdate=13 March 2019}}</ref> In 2002, Flannery was appointed as chair of South Australia's [Environmental Sustainability Board (South Australia)].<ref>{{Cite web|title = Mike Rann's politics of the possible - South Australia|url = http://www.companydirectors.com.au/Director-Resource-Centre/Publications/Company-Director-magazine/2000-to-2009-back-editions/2002/September/Mike-Ranns-politics-of-the-possible-Centre-Liftout-South-Australia|website = Australian Institute of Company Directors|accessdate = 2015-06-09|date = 2002-09-01|archive-date = 11 November 2020|archive-url = https://web.archive.org/web/20201111172321/http://www.companydirectors.com.au/Director-Resource-Centre/Publications/Company-Director-magazine/2000-to-2009-back-editions/2002/September/Mike-Ranns-politics-of-the-possible-Centre-Liftout-South-Australia|url-status = dead}}</ref>
In 2007, Flannery became professor in the Climate Risk Concentration of Research Excellence at [[Macquarie University]]. He left Macquarie University in mid-2013. Flannery is also a member of the [[Wentworth Group of Concerned Scientists]], and a Governor of WWF-Australia. He has contributed to over 143 scientific papers.{{citation needed|date=March 2013}} <ref name="The Weather Makers-About TF" />
Flannery was an advisor on climate change to South Australian Premier [[Mike Rann]], and was a member of the Queensland Climate Change Council established by the Queensland Minister for Sustainability, Climate Change and Innovation [[Andrew McNamara]]. In February 2011 it was announced that Flannery had been appointed to head the ''Climate Change Commission'' established by Prime Minister [[Julia Gillard]] to explain climate change and the need for a [[Carbon pricing|carbon price]] to the public.<ref>{{cite web|last=Morton|first=Adam|title=Rudd critic to lead climate team|url=http://www.theage.com.au/environment/climate-change/rudd-critic-to-lead-climate-team-20110210-1aoqk.html|work=[[The Age]]|publisher=[[Fairfax Media]]|accessdate=15 February 2011|date=11 February 2011}}</ref>
He owns a house with environmental features at Coba Point on the [[Hawkesbury River]], 40 km (25 mi) north of Sydney, accessible only by boat.<ref>https://www.quarterlyessay.com.au/correspondence/correspondence-tim-flannery</ref>
== Climate Commission and Climate Council ==
On 10 February 2011, Flannery was appointed as the Chief Commissioner of the [[Climate Commission]] by the Australian Government. The Commission was a panel of leading scientists and business experts whose mandate was to provide an "independent and reliable" source of information for all Australians.<ref name=About>{{cite web| title=About the Commission| url=http://climatecommission.gov.au/about/| publisher=Climate Commission| accessdate=24 September 2013| url-status=dead| archiveurl=https://web.archive.org/web/20130827044300/http://climatecommission.gov.au/about/| archivedate=27 August 2013| df=dmy-all}}</ref>
On 19 September 2013, Flannery was sacked from his position as head of the Climate Commission in a phone call from new Federal Environment Minister [[Greg Hunt]]. It was also announced that the Commission would be dismantled and its remit handled by the [[Department of Sustainability, Environment, Water, Population and Communities|Department of Environment]].<ref name="sacked/dismantled">{{cite news| author=Jones, Gemma| title=Tim Flannery sacked, Climate Commission dismantled by Coalition| url=http://www.news.com.au/national-news/tim-flannery-sacked-climate-commission-dismantled-by-coalition/story-fncynjr2-1226722779566| accessdate=19 September 2013| newspaper=news.com.au| date=19 September 2013| archive-date=10 October 2013| archive-url=https://web.archive.org/web/20131010043409/http://www.news.com.au/national-news/tim-flannery-sacked-climate-commission-dismantled-by-coalition/story-fncynjr2-1226722779566| url-status=dead}}</ref><ref>{{cite news|url=http://www.theage.com.au/federal-politics/political-news/abbott-shuts-down-climate-commission-20130919-2u185.html | title=Abbott shuts down Climate Commission |author=Tom Arup |newspaper=theage.com.au |date= 2013-09-19|accessdate=27 October 2013}}</ref>
By 6 October 2013, Flannery and the other commissioners had launched a new body called the [[Climate Council]]. Flannery told ABC News that the organisation stated that it had the same goals as the former Climate Commission, to provide independent information on the science of climate change. [[Amanda McKenzie]] was appointed as CEO. Between 24 September and 6 October the new Climate Council had raised $1 million in funding from a public appeal, sufficient to keep the organisation operating for 12 months.<ref>{{cite news| title=Climate Council, which replaced the axed Climate Commission, reaches $1 million funding target| url=http://www.abc.net.au/news/2013-10-05/climate-council-has-sufficient-funding-to-operate-for-a-year/5000822| accessdate=15 October 2013|publisher=ABC| date= 6 October 2013}}</ref>
== Scientific contributions ==
===Mammalogy ===
Flannery's early research concerned the evolution of mammals in Australasia. As part of his doctoral studies, he described 29 new fossil kangaroo species including 11 new genera and three new subfamilies. In the 1990s, Flannery published ''The Mammals Of New Guinea'' (Cornell Press) and ''Prehistoric Mammals Of Australia and New Guinea'' (Johns Hopkins Press), the most comprehensive reference works on the subjects. Through the 1990s, Flannery surveyed the mammals of [[Melanesia]] – discovering 29 new species – and took a leading role in conservation efforts in the region.<ref name="abc.net.au">{{cite web | title = The Future Eaters: About Tim Flannery | publisher = [[ABC Television (Australia)|ABC Television]] | year = 1998 | url = http://www.abc.net.au/science/future/abouttim.htm | accessdate = 10 April 2007 }}</ref>
The [[specific name (zoology)|specific name]] of the [[greater monkey-faced bat]] (''Pteralopex flanneryi''), [[Bats discovered in the 2000s|described in 2005]], honours Flannery.<ref>Helgen, K. M. (2005). "Systematics of the Pacific monkey-faced bats (Chiroptera : Pteropodidae), with a new species of Pteraloplex and a new Fijian genus". ''Systematics and Biodiversity'', '''3'''(4): 433–453.</ref>
Flannery's work prompted [[David Attenborough|Sir David Attenborough]] to describe him as being "in the league of the all-time great explorers like Dr [[David Livingstone]]".<ref>{{cite web | title = Penguin UK Authors: About Tim Flannery | publisher = [[Penguin Books]] | url = http://www.penguin.co.uk/nf/Author/AuthorPage/0,,0_1000056817,00.html?sym=BIO | accessdate = 10 April 2007 | url-status = dead | archiveurl = https://web.archive.org/web/20070930155226/http://www.penguin.co.uk/nf/Author/AuthorPage/0,,0_1000056817,00.html?sym=BIO | archivedate = 30 September 2007 | df = dmy-all }}</ref>
=== Palaeontology ===
In 1980, Flannery discovered [[dinosaur]] [[fossil]]s on the southern coast of [[Victoria (Australia)|Victoria]] and in 1985 had a role in the ground-breaking discovery of [[Cretaceous]] mammal fossils in Australia. This latter find extended the Australian mammal fossil record back 80 million years. During the 1980s, Flannery described most of the known [[Pleistocene]] megafaunal species in [[New Guinea]] as well as the fossil record of the [[phalangerid]]s, a family of possums.<ref name="abc.net.au"/>
===Work on population and land use===
In 1994, Flannery published ''The Future Eaters: An Ecological History of the Australasian Lands and People''.
The synopsis of the work regards three waves of human migration in these regions. These waves of people Flannery describes as "future eaters". The first wave was the migration to Australia and New Guinea from [[Southeast Asia]] approximately 40 000 – 60 000 years ago. The second was Polynesian migration to New Zealand and surrounding islands 800 – 3500 years ago.<ref>{{cite book|last=Flannery|first=Timothy Fridtjof|title=The future eaters: an ecological history of the Australasian lands and people|url=https://archive.org/details/isbn_0730104877|url-access=registration|year=1994|publisher=Reed New Holland|location=Sydney|isbn=978-1-876334-21-5|pages=[https://archive.org/details/isbn_0730104877/page/242 242]}}</ref> The third and final wave Flannery describes is European colonisation at the end of the 18th century.
Flannery describes the evolution of the first wave of future-eaters:
{{quote|Sixty thousand or more years ago human technology was developing at what we would consider to be an imperceptible pace. Yet it was fast enough to give the first Australasians complete mastery over the ‘new lands’. Freed from the ecological constraints of their homeland and armed with weapons honed in the relentless arms race of Eurasia, the colonisers of the ‘new lands’ were poised to become the world’s first future eaters.<ref>{{cite book|last=Flannery|first=Timothy Fridtjof|title=The future eaters: an ecological history of the Australasian lands and people|url=https://archive.org/details/isbn_0730104877|url-access=registration|year=1994|publisher=Reed New Holland|location=Sydney|isbn=978-1-876334-21-5|pages=[https://archive.org/details/isbn_0730104877/page/143 143]}}</ref>}}
While the book continues to be controversial in some of its hypotheses, it is a call to arms to preserve the Australasian natural heritage.
Flannery argues the hypothesis that at current population growth rate levels, Australasia is living beyond its population carrying capacity, to the extent that its biological stability has been damaged. European colonisation of Australia and New Caledonia brought its own artefacts and ways suitable in the ‘old world’, and yet struggle to adapt its "culture to biological reality".<ref>{{cite book|last=Flannery|first=Timothy Fridtjof|title=The future eaters: an ecological history of the Australasian lands and people|url=https://archive.org/details/isbn_0730104877|url-access=registration|year=1994|publisher=Reed New Holland |location=Sydney|isbn=978-1-876334-21-5|pages=[https://archive.org/details/isbn_0730104877/page/374 374], 389}}</ref>{{Non-primary source needed|date=December 2018}} This reality is evident in Australia, where unpredictable climate combined with a lack of natural life giving resources have created a flora and fauna that have adapted over millennia to be extraordinarily efficient in the consumption of energy.<ref>{{cite book|last=Flannery|first=Timothy Fridtjof|title=The future eaters: an ecological history of the Australasian lands and people|url=https://archive.org/details/isbn_0730104877|url-access=registration|year=1994|publisher=Reed New Holland|location=Sydney|isbn=978-1-876334-21-5|pages=[https://archive.org/details/isbn_0730104877/page/85 85–91]}}</ref>{{Non-primary source needed|date=December 2018}}
''The Future Eaters'' enjoyed strong sales and critical acclaim. Redmond O'Hanlon, a ''[[The Times Literary Supplement|Times Literary Supplement]]'' correspondent said that "Flannery tells his beautiful story in plain language, science popularising at its antipodean best". Fellow activist [[David Suzuki]] praised Flannery's "powerful insight into our current destructive path". Some experts disagreed with Flannery's thesis, however, concerned that his broad-based approach, ranging across multiple disciplines, ignored counter-evidence and was overly simplistic.<ref name="abc-fe">{{cite web | title = The Future Eaters | publisher = [[ABC Television (Australia)|ABC Television]] | year = 1998 | url = http://www.abc.net.au/science/future/theses/theses.htm | accessdate = 10 April 2007 }}</ref>
''The Future Eaters'' was made into a documentary series for [[ABC Television (Australia)|ABC Television]] and was republished in late 2013.
==Views on environmental issues==
===Climate change===
In May 2004 Flannery said, in light of the city's water crisis, that, "I think there is a fair chance Perth will be the 21st century's first ghost metropolis".,<ref>{{cite news|url=http://www.smh.com.au/articles/2004/05/18/1084783517732.html |title=Sydney's future eaten: the Flannery prophecy |author=Davis, Anne|date=19 May 2004|work=[[Sydney Morning Herald]]|accessdate=24 July 2016}}</ref> a warning reiterated in 2007.<ref>{{cite news|url=http://www.perthnow.com.au/news/western-australia/flannery-sticks-by-ghost-city/news-story/49cec9bf446f6e3be9e730e29fe52476 |title=Flannery sticks by 'ghost' city |author=Premble, Louise|date=17 February 2007|work=Perth Now|accessdate=17 February 2017}}</ref> In April 2005, he said, "water is going to be in short supply across the eastern states".<ref>{{cite news|url=http://www.smh.com.au/news/Opinion/Running-out-of-water--and-time/2005/04/24/1114281450815.html |title=Running out of water - and time |date=25 April 2005|work=[[Sydney Morning Herald]]|accessdate=24 July 2016}}</ref> In June 2005 warning that "the ongoing drought could leave Sydney’s dams dry in just two years".<ref>{{cite news|url=http://www.abc.net.au/news/2005-06-11/climate-change-will-prolong-drought-conditions/1590294 |title=Climate change 'will prolong' drought conditions |date=11 June 2005|work=[[ABC News (Australia)|ABC News]]|accessdate=24 July 2016}}</ref><ref>{{cite news|url=http://www.abc.net.au/lateline/flannery-issues-global-warming-warning/1590650 |title= Flannery issues global warming warning|author=|date=10 June 2005|work=[[ABC News (Australia)|ABC News]]|accessdate=25 October 2017}}</ref> Water security remains a major issue across eastern Australia.
In September 2005 Flannery said, "There are hot rocks in South Australia that potentially have enough embedded energy in them to run Australia's economy for the best part of a century".<ref>{{cite news|url=http://www.abc.net.au/lateline/content/2006/s1842715.htm |title= Tony Jones speaks with Tim Flannery|author=Jones, Tony|date=7 February 2007|work=[[ABC News (Australia)|ABC News]]|accessdate=1 September 2016}}</ref><ref>{{cite news|url=http://www.smh.com.au/news/world/the-power-beneath-our-feet/2005/09/26/1127586753959.html |title=The power beneath our feet |author=Flannery, Tim|date=26 September 2005|work=[[Sydney Morning Herald]]|accessdate=1 September 2016}}</ref> Also for the [[Cooper Basin]], he proposed the establishment of a fully sustainable city where, "hundreds of thousands of people would live", utilising these geothermal energy reserves. He named the city, "Geothermia".<ref name = "Geothermia"/><ref>{{cite news|url=http://www.ias.uwa.edu.au/new-critic/nine/hodgkinson |title=Review - Tim Flannery’s ‘Now or Never |author=Hodgkinson, David|date=December 2008|work=Institute of Advanced Studies, [[University of Western Australia]]|accessdate=1 September 2016}}</ref> Subsequently, in 2007, an exploration company was established. The company expected to raise at least $11.5m on the [[Australian Stock Exchange]].<ref>{{cite news |url=http://www.aie.org.au/pubs/hotdry.htm |title="Hot Dry Rock" company float to fund South Australian pilot plant |date=25 October 2007 |work=Geodynamics Limited |accessdate=1 September 2016 |url-status=bot: unknown |archiveurl=https://web.archive.org/web/20080723035649/http://www.aie.org.au/pubs/hotdry.htm |archivedate=23 July 2008 }} {{Webarchive|url=https://web.archive.org/web/20080723035649/http://www.aie.org.au/pubs/hotdry.htm |date=23 July 2008 }}</ref> Flannery took up shares in the company.<ref>{{cite web|url=http://www.hotdryrocks.com/content/view/39/2/|title=Tim Flannery named Australia of the Year|date=2007|work=Hot Dry Rocks Pty Ltd|accessdate=3 September 2016|archive-date=16 September 2016|archive-url=https://web.archive.org/web/20160916103306/http://www.hotdryrocks.com/content/view/39/2/|url-status=dead}}</ref> In 2010, the Federal Government provided the company with another $90m for the development work.<ref>{{cite news|url=http://www.geodynamics.com.au/getattachment/b1e346be-2a3f-42cc-b0b1-33c2b6472a9c/$90-m-REDP-grant-for-Geodynamics-now-unconditional.aspx|title=$90 m REDP grant for Geodynamics now unconditional|date=13 October 2010|work=Geodynamics Limited|accessdate=1 September 2016|archive-date=9 March 2016|archive-url=https://web.archive.org/web/20160309065745/http://www.geodynamics.com.au/getattachment/b1e346be-2a3f-42cc-b0b1-33c2b6472a9c/$90-m-REDP-grant-for-Geodynamics-now-unconditional.aspx|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20160309065745/http://www.geodynamics.com.au/getattachment/b1e346be-2a3f-42cc-b0b1-33c2b6472a9c/$90-m-REDP-grant-for-Geodynamics-now-unconditional.aspx |date=9 March 2016 }}</ref> In August 2016, the geothermal energy project closed as it was not financially viable.<ref>{{cite news|url=http://www.adelaidenow.com.au/business/geodynamics-calls-it-a-day-with-its-south-australian-geothermal-energy-plans/news-story/8ffdf4e24b633e5a41e4afd4f6787f2a |title=Geodynamics calls it a day with its South Australian geothermal energy plans |author=England, Cameron|date=29 August 2016|work=Adelaide Now|accessdate=1 September 2016}}</ref><ref>{{cite news|url=http://www.abc.net.au/news/2016-08-30/geothermal-power-plant-closes-deemed-not-financially-viable/7798962 |title=Geothermal power project closes in SA as technology deemed not financially viable |author= Fedorowytsch, Tom|date=30 August 2016|work=[[ABC News (Australia)|ABC News]]|accessdate=1 September 2016}}</ref>
In October 2006 Flannery quoted a US Navy study stating that, there may be, "no Arctic icecap in Summer in the next five to 15 years. He also quoted NASA's Professor [[James Hansen]], "arguably the world authority on climate change" who said, "we have just a decade to avert a 25-metre rise of the sea".<ref>{{cite news|url=http://www.theage.com.au/news/opinion/climates-last-chance/2006/10/27/1161749313108.html |title=Climate's last chance |author=Flannery, Tim|date=28 October 2006|work=[[The Age]]|accessdate=24 July 2016}}</ref> In February 2007, as he explained how increased soil evaporation impacts on runoff, he said "even the [existing amount of] rain that falls isn’t actually going to fill our dams and our river systems" <ref>{{cite news|url=http://www.abc.net.au/tv/programs/landline/old-site/content/2006/s1844398.htm|title=Interview with Professor Tim Flannery|author=Sara, Sally|date=11 February 2007|work=Landline, [[ABC News (Australia)|ABC News]]|accessdate=28 December 2018}}</ref> and in June 2007, he said that, "Adelaide, Sydney and Brisbane, water supplies are so low they need desalinated water urgently, possibly in as little as 18 months".<ref>{{cite web|url=http://www.sciencearchive.org.au/nova/newscientist/105ns_001.htm|title=Editorial: Australia – not such a lucky country|date=16 June 2007|work=[[New Scientist]]|accessdate=24 July 2016|url-status=bot: unknown|archiveurl=https://web.archive.org/web/20140524055921/http://www.sciencearchive.org.au/nova/newscientist/105ns_001.htm|archivedate=24 May 2014|df=dmy-all}}</ref>
=== Carbon emissions ===
In ''[[The Weather Makers|The Weather Makers: The History & Future Impact of Climate Change]]'', Flannery outlined the science behind anthropogenic [[climate change]]. "With great scientific advances being made every month, this book is necessarily incomplete," Flannery writes, but "That should not, however, be used as an excuse for inaction. We know enough to act wisely."
Concepts outlined in the book include:
* That a failure to act on climate change may eventually force the creation of a global carbon dictatorship, which he calls the "Earth Commission for Thermostatic Control", to regulate carbon use across all industries and nations – a level of governmental intrusion that Flannery describes as "very undesirable";<ref>{{cite web | title = Hurricanes can be tied to climate change | first = Tony | last = Jones | work = Lateline | publisher = [[ABC Television (Australia)|ABC Television]] | date = 26 September 2005 | url = http://www.abc.net.au/lateline/content/2005/s1468878.htm | accessdate = 10 April 2007 }}</ref> and
* the establishment of "Geothermia"<ref name = "Geothermia">{{cite book|url=https://books.google.com.au/books?id=_4mzCgAAQBAJ&pg=PT297&lpg=PT297&dq=flannery+Geothermia&source=bl&ots=0aj5ltBv9n&sig=gGaEKTX_O0wpHUdh7dAwA_qP7og&hl=en&sa=X&ved=0ahUKEwic5PHai-3OAhVFqJQKHeVcC984ChDoAQhFMAk#v=onepage&q=flannery%20Geothermia&f=false |title=The Moral Vacuum |author=Gabe, Martyn|date=2009| accessdate=1 September 2016}}</ref> – a new city at the NSW-South Australia-Queensland border – to take advantage of the location's abundance of natural gas reserves, geothermal and solar energy. Flannery argues that such a city could be completely energy self-sufficient, and would be a model for future city development worldwide. Of the city project, Flannery told ''The Bulletin'' that "I know it's radical but we have no choice".
The book won international acclaim. [[Bill Bryson]] concluded that "It would be hard to imagine a better or more important book." ''The Weather Makers'' was honoured in 2006 as 'Book of the Year' at the New South Wales Premier's Literary Awards.<ref>{{cite web | title = The Weather Makers: All About the Book | publisher = [[Text Publishing]] | year = 2006 | url = http://www.theweathermakers.com/weathermakers/ | accessdate = 10 April 2007 | url-status = dead | archiveurl = https://web.archive.org/web/20070322222441/http://www.theweathermakers.com/weathermakers/ | archivedate = 22 March 2007 | df = dmy-all }}</ref>
Flannery's work in raising the profile of environmental issues was key to his being named Australian of the Year in 2007.<ref>{{cite book | author=Lewis, Wendy | title=Australians of the Year | publisher=Pier 9 Press | year=2010 | isbn=978-1-74196-809-5 | authorlink=Wendy Lewis }}</ref> Awarding the prize, former Prime Minister [[John Howard]] said that the scientist "has encouraged Australians into new ways of thinking about our environmental history and future ecological challenges."<ref>{{cite news | title = Climate change crusader is Australian of the Year | newspaper = [[The West Australian]] | date = 25 January 2007 | url = http://www.thewest.com.au/default.aspx?MenuID=145&ContentID=19672 | accessdate = 10 April 2007 | archiveurl = https://web.archive.org/web/20070927192549/http://www.thewest.com.au/default.aspx?MenuID=145&ContentID=19672 | archivedate = 27 September 2007 }} {{Webarchive|url=https://web.archive.org/web/20070927192549/http://www.thewest.com.au/default.aspx?MenuID=145&ContentID=19672 |date=27 September 2007 }}</ref>
That said, Howard – along with many others – remains unconvinced as to Flannery's proposed solutions. Flannery joined calls for the cessation/reduction of conventional coal-fired power generation in Australia in the medium term, the source of most of the nation's electricity. Flannery claims that conventional coal burning will lose its social license to operate, as has asbestos.<ref>{{cite news | title = Coal will be the new asbestos, says Flannery | newspaper = [[The Sydney Morning Herald]] | date = 9 February 2007 | url = http://www.smh.com.au/news/environment/coal-will-be-the-new-asbestos-says-flannery/2007/02/08/1170524236625.html | accessdate = 10 April 2007 }}</ref>
[[File:2014-09-21 Tim Flannery Peoples Climate March Melbourne 600 0485.JPG|thumb|Tim Flannery speaking at the Peoples Climate March in Melbourne, September 2014]]
In response to the introduction of proposed [[clean coal technology]], Flannery has stated: "Globally there has got to be some areas where clean coal will work out, so I think there will always be a coal export industry [for Australia] ... Locally in Australia because of particular geological issues and because of the competition from cleaner and cheaper energy alternatives, I'm not 100 per cent sure clean coal is going to work out for our domestic market."<ref>[http://www.news.com.au/heraldsun/story/0,21985,21225432-661,00.html "Coal Can't Be Clean – Flannery"] {{Webarchive|url=https://web.archive.org/web/20090805051322/http://www.news.com.au/heraldsun/story/0,21985,21225432-661,00.html |date=5 August 2009 }}, ''Melbourne Herald Sun'', 14 February 2007.</ref>
In 2006 Flannery was in support of [[nuclear power]] as a possible solution for reducing Australia's [[carbon emissions]],<ref>{{cite magazine | first = Julie-Anne | last = Davies | title = Dr Flannery, I presume | magazine = [[The Bulletin (Australian periodical)|The Bulletin]] | date = 23 February 2007 | url = http://bulletin.ninemsn.com.au/article.aspx?id=228258 | accessdate = 10 April 2007 | url-status = dead | archiveurl = https://web.archive.org/web/20070403095709/http://bulletin.ninemsn.com.au/article.aspx?id=228258 | archivedate = 3 April 2007 | df = dmy-all }}</ref><ref>{{cite news| url=http://www.theage.com.au/news/opinion/lets-talk-about-nuclear-power-emandem-other-energy-sources/2006/05/29/1148754933159.html | location=Melbourne | work=The Age | title=Let's talk about nuclear power ''and'' other energy sources | date=30 May 2006}}</ref> however in 2007 changed his position against it.<ref>[http://www.crikey.com.au/2009/02/05/flip-flop-flannery-is-a-climate-change-opportunist/ Clive Hamilton:''Flip-flop Flannery is a climate change opportunist'', in Crikey 5 February 2009], retrieved 17 June 2010</ref> In May 2007 he told a business gathering in Sydney that while nuclear energy does have a role elsewhere in the world, Australia's abundance of renewable resources rule out the need for nuclear power in the near term. He does however feel that Australia should and will have to supply its uranium to those other countries that do not have access to renewables like Australia does.<ref>{{cite news| url=http://www.smh.com.au/news/national/nuclear-power-a-turnoff-flannery-changes-stance/2007/05/22/1179601413336.html | work=The Sydney Morning Herald | title=Nuclear power a turn-off: Flannery changes stance | date=23 May 2007}}</ref>
In May 2008 Flannery created controversy by suggesting that sulphur could be dispersed into the atmosphere to help block the sun leading to global dimming, in order to counteract the effects of global warming.<ref name="cm-dim">{{cite news|last=Alexander|first=Cathy|url=http://www.news.com.au/top-stories/climate-plan-could-change-sky-colour/story-e6frfkp9-1111116384553|title=Tim Flannery's radical climate change 'solution'|date=19 May 2008|work=[[News.com.au]]|agency=[[Australian Associated Press]]|accessdate=23 April 2011|archive-date=7 July 2011|archive-url=https://web.archive.org/web/20110707210342/http://www.news.com.au/top-stories/climate-plan-could-change-sky-colour/story-e6frfkp9-1111116384553|url-status=dead}}</ref>
In August 2017 Flannery hosted an episode of [[Catalyst (TV program)|ABC Catalyst]] investigating how carefully managed seaweed growth could contribute to combating climate change via the sequestration of atmospheric carbon to the ocean floor. In January 2018 Flannery appeared on the ABC's Science program exploring whether humans are becoming a new [https://www.youtube.com/watch?v=Lw-hTF2EvWk&t=0s&index=17&list=PLJI8aSIWm2aB2rWQLI-flwILAgbNAx9xt 'Mass Extinction Event'], in addition to outlining the [https://www.youtube.com/watch?v=ba2y9xSzjUk&list=PLJI8aSIWm2aB2rWQLI-flwILAgbNAx9xt&index=1 '5 Things You Need to Know About Climate Change'].
=== Sustainable whaling ===
When, in the concluding chapters of ''The Future Eaters'' (1994), Flannery discusses how to "utilise our few renewable resources in the least destructive way", he remarks that
{{quote|A far better situation for conservation in Australia would result from a policy which allows exploitation of ''all'' of our biotic heritage, provided that it all be done ''in a sustainable manner''. ... [I]f it is possible to harvest for example, 10 [[mountain pygmy-possum]]s (''Burramys parvus'') or 10 [[southern right whale]]s (''Balaena glacialis'') per year, why should we not do it? ... Is it more moral to kill and consume a whale, without cost to the environment, than to live as a vegetarian in Australia, destroying seven kilograms of irreplaceable soil, ... for each kilogram of bread we consume?<ref>Tim Flannery, ''The Future Eaters'', pp. 402–403. {{ISBN|0-8021-3943-4}}</ref>}}
In late 2007, Flannery suggested that the [[Whaling in Japan|Japanese whaling]] involving the relatively common [[minke whale]] may be [[sustainable]]:
{{quote|In terms of sustainability, you can't be sure that the Japanese whaling is entirely unsustainable... It's hard to imagine that the whaling would lead to a new decline in population<ref name=whaling2>[http://www.news.com.au/dailytelegraph/story/0,22049,22987914-5001021,00.html Flannery says whaling is OK.] {{Webarchive|url=https://web.archive.org/web/20090604182354/http://www.news.com.au/dailytelegraph/story/0,22049,22987914-5001021,00.html |date=4 June 2009 }} ''The Daily Telegraph''. Retrieved on 2 January 2008</ref>}}
This raised concerns among some environmental groups such as Greenpeace,<ref name=whaling4>[http://news.smh.com.au/flannerys-views-on-whales-curious/20071231-1jmg.html Flannery's views on whales 'curious'.] {{webarchive|url=https://web.archive.org/web/20080306054308/http://news.smh.com.au/flannerys-views-on-whales-curious/20071231-1jmg.html |date=6 March 2008 }} ''The Sydney Morning Herald''. Retrieved on 2 January 2008</ref><ref name=whaling1>[http://www.livenews.com.au/Articles/2007/12/31/Australian_of_the_year_reveals_his_support_for_whaling Tim Flannery lampooned by sustainable whaling claims.] {{webarchive|url=https://web.archive.org/web/20071231211609/http://www.livenews.com.au/articles/2007/12/31/Australian_of_the_year_reveals_his_support_for_whaling |date=31 December 2007 }} LiveNews. Retrieved on 2 January 2008</ref> fearing it could add fuel to the Japanese wish of continuing its annual cull. In contrast to his stance on the minke whale quota, Flannery has expressed relief over the dumping of the quota of the rarer [[humpback whale]],<ref name=whaling2/> and further was worried how [[whales]] were slaughtered, wishing them to be "killed as humanely as possible".<ref name=whaling3>[http://www.brisbanetimes.com.au/news/national/flannery-worried-about-small-fish-not-big-whale-culls/2007/12/31/1198949702750.html Flannery worried about small fish, not big whale culls.] ''Brisbane Times''. Retrieved on 2 January 2008</ref> Flannery suggested that [[krill]] and other small [[crustaceans]], the primary food source for many large whales and an essential part of the marine [[food chain]], were of greater concern than the Japanese whaling.<ref name=whaling3/>
=== Species acclimatisation ===
[[File:Catagonus wagneri closeup.jpg|thumb|The Chacoan Peccary can be brought from Paraguay to North America, to replace the extinct Flat-headed Peccary]]
In ''The Future Eaters'', Flannery was critical of the European settlers [[Acclimatisation society|introducing]] [[Introduced species|non-native]] wild animals into Australia's ecosystem. At the same time, he suggested that if one wanted to reproduce, in some parts of Australia, the ecosystems that existed there ca. 60,000 years ago (before the arrival of the humans on the continent), it may be necessary to introduce into Australia, in a thoughtful and careful way, some non-native species that would be the closest substitutes to the continent's lost [[Australian megafauna|megafauna]]. In particular, the [[Komodo dragon]] can be brought into Australia as a replacement for its extinct relative, ''[[Megalania]]'', "the largest [[goanna]] of all time". The [[Tasmanian devil]] could also be allowed to re-settle the mainland Australia from its Tasmanian refuge area.<ref>Tim Flannery, ''The Future Eaters'', pp. 384–385. {{ISBN|0-8021-3943-4}}</ref>
In ''The Eternal Frontier'', Flannery made a proposal for what later became nicknamed "[[Pleistocene rewilding]]": restoring the ecosystems that existed in North America before the arrival of the [[Clovis culture|Clovis people]] and the concomitant disappearance of the North American [[Pleistocene megafauna]] ca. 13,000. He wonders if, in addition to the wolves that have been already re-introduced to [[Yellowstone National Park]], ''ambush predators'', such as [[jaguar]]s and lions should be reintroduced there as well, in order to bring the number of [[elk]]s under control. Furthermore, the closest extant relatives of the species that became extinct around the Clovis period could be introduced to North America's nature reserves as well. In particular, the [[Indian elephant|Indian]] and [[African elephant|African]] elephants could substitute, respectively, for the [[mammoth]] and the [[mastodon]]; the [[Chacoan Peccary]], for its extinct cousin the flat-headed Peccary (''[[Platygonus compressus]]''). [[Llama]]s and [[Panthera|panther]]s, which still survive outside of the US, should too be brought back to that country.<ref>Flannery, ''The Eternal Frontier'', {{ISBN|1-876485-72-8}}, pp. 345–346. On the peccary, p. 158</ref>
== Activism ==
Flannery has achieved prominence through his [[environmental activism]]. His advocacy on two issues in particular, population levels and carbon emissions, culminated in being named [[Australian of the Year]] (2007) at a time when environmental issues were becoming prominent in Australian public debate.{{Citation needed|date=December 2018}}
He is a member of the [[World Future Council]].{{Citation needed|date=December 2018}}
== Humanitarian ==
In 2009, Flannery joined the project "Soldiers of Peace", a move against all wars and for a global peace.<ref>{{cite web |url=http://www.soldiersofpeacemovie.com/about/the-cast/18/tim-flannery/ |title=Tim Flannery — The Cast — Soldiers of Peace |publisher=Soldiersofpeacemovie.com |accessdate=18 October 2009 |url-status=dead |archiveurl=https://web.archive.org/web/20090808104958/http://www.soldiersofpeacemovie.com/about/the-cast/18/tim-flannery/ |archivedate=8 August 2009 |df=dmy-all }}</ref><ref>{{cite web|url=http://www.soldatidipace.blogspot.com/ |title=Soldati di Pace (Soldiers of Peace) |publisher=Soldatidipace.blogspot.com |date=18 October 2009 |accessdate=18 October 2009}}</ref>
In July 2018 he played a role in the Kwaio Reconciliation programme in the Solomon Islands, which put an end to a 91-year-old cycle of killings that stemmed from the murders in 1927 of British Colonial officers Bell and Gillies by Kwaio leader Basiana and his followers.{{Citation needed|date=December 2018}}
==Awards==
* Edgeworth David Medal for outstanding research in zoology
* [[Centenary of Federation Medal]] for his services to Australian science
* [[Colin Roderick Award]], Foundation for Australian Literary Studies for ''Tree Kangaroos'' (1996)
* First environmental scientist to deliver the Australia Day address to the nation (2002).
* [[Australian Humanist of the Year]] (2005)
* NSW Australian of the Year (2006)
* Australian of the Year (2007)
* NSW Premier's Literary Prizes for Best Critical Writing and Book of the Year (''The Weather Makers'', 2006).
* US Lannan Award for Non-fiction works (2006).
* [[The New York Times Best Seller list]] (''The Weather Makers'')
* [[Order of Saint-Charles]], Monaco
* [[Leidy Award]] (2010)<ref>{{cite news |last=Mitchell |first=Peter |date=3 November 2010 |title=Flannery wins Joseph Leidy Award |url=http://www.smh.com.au/environment/conservation/flannery-wins-joseph-leidy-award-20101102-17dl5.html |newspaper=The Sydney Morning Herald |location=Sydney |access-date=15 April 2015}}</ref>
* [[Fellow of the Australian Academy of Science]] (2012)<ref>{{cite web |url= https://www.science.org.au/fellowship/fellows/professor-timothy-fritjof-flannery |title= Professor Timothy Fritjof Flannery |publisher= Australian Academy of Science |date= 2012 |access-date= 6 July 2020 |archive-date= 26 December 2017 |archive-url= https://web.archive.org/web/20171226075600/https://www.science.org.au/fellowship/fellows/professor-timothy-fritjof-flannery |url-status= dead }}</ref>
*Jack P. Blaney Award for Dialogue (2015/2016).<ref name="sfu.ca"/>
==Bibliography==
{{Expand list|date=March 2018}}
===Books===
*{{cite book |author=Flannery, Timothy |authormask= |title=Mammals of New Guinea |location=Carina, Qld. |publisher=Robert Brown & Associates |year=1990 |<!--isbn=1862730296-->}}
*Tim Flannery (1994), ''[[The Future Eaters]]: An Ecological History of the Australasian Lands and People'' ({{ISBN|0-8021-3943-4}} and {{ISBN|0-7301-0422-2}}).
*Tim Flannery (1994), ''Possums of the World : Monograph of the Phalangeroidea'' ({{ISBN|0-646-14389-1}}).
*{{cite book |author=Flannery, Timothy |authormask= |title=Mammals of New Guinea |location=Chatswood, NSW |publisher=Reed/Australian Museum |year=1995 |edition=New <!--isbn=0730104117-->}}
*Tim Flannery (1995), ''Mammals of the South-West Pacific & Moluccan Islands'' ({{ISBN|0-7301-0417-6}}).
*Tim Flannery, Roger Martin and Alexandra Szalay. (1996) ''Tree Kangaroos: A Curious Natural History''.
*Tim Flannery (1998), [[Throwim Way Leg|''Throwim Way Leg: An Adventure'']] ({{ISBN|1-876485-19-1}}).
*Tim Flannery (2001), ''The Eternal Frontier: An Ecological History of North America and its Peoples'' ({{ISBN|0-8021-3888-8}}).
*John A. Long, Michael Archer, Tim Flannery and Suzanne Hand (2002), ''Prehistoric Mammals of Australia and New Guinea: One Hundred Million Years of Evolution'', Johns Hopkins Press ({{ISBN|978-0-801872-23-5}}).
*Tim Flannery & [[Peter Schouten]] (2001), ''A Gap in Nature'' ({{ISBN|1-876485-77-9}}).
*Tim Flannery & Peter Schouten (2004), ''Astonishing Animals'' ({{ISBN|1-920885-21-8}}).
*Tim Flannery (2005), ''Country: A Continent, a Scientist & a Kangaroo'' ({{ISBN|1-920885-76-5}}).
*Tim Flannery (2005), ''[[The Weather Makers|The Weather Makers: The History & Future Impact of Climate Change]]'' ({{ISBN|1-920885-84-6}}).
*Tim Flannery (2007), ''[[Chasing Kangaroos]]: A Continent, a Scientist, and a Search for the World's Most Extraordinary Creature'' ({{ISBN|978-0-8021-1852-3}}).
*Tim Flannery (2009), ''Now or Never: A sustainable future for Australia?'' ({{ISBN|978-1-86395-429-7}}).<ref>[http://www.blackincbooks.com/books/now-or-never ''Now or Never: A sustainable future for Australia?''] {{Webarchive|url=https://web.archive.org/web/20160304071701/http://www.blackincbooks.com/books/now-or-never |date=4 March 2016 }}. Melbourne: Black Inc. Books ({{ISBN|978-1-86395-429-7}}).</ref>
*Tim Flannery (2009), ''Now or Never: Why we need to act now for a sustainable future'' ({{ISBN|978-1-55468-604-9}}).<ref>[http://www.harpercollins.ca/books/9781554686049/Now_or_Never/index.aspx ''Now or Never: Why we need to act now for a sustainable future''] {{Webarchive|url=https://web.archive.org/web/20150924025102/http://www.harpercollins.ca/books/9781554686049/Now_or_Never/index.aspx |date=24 September 2015 }}, Harper Collins ({{ISBN|978-1-55468-604-9}}).</ref>
*Tim Flannery (2010), ''Here on Earth'', {{ISBN|978-1-921656-66-8}}<ref>[http://www.nybooks.com/articles/archives/2011/oct/13/can-our-species-escape-destruction/ Can Our Species Escape Destruction?] 13 October 2011 by [[John Terborgh]] in [[The New York Review of Books]]</ref>
*Tim Flannery (2011), ''Among the Islands: Adventures in the Pacific'' ({{ISBN|978-1-921758-75-1}}).
*Tim Flannery (2015), ''Atmosphere of Hope: Searching for Solutions to the Climate Crisis'', Boston: Atlantic Monthly Press ({{ISBN|978-0802124067}}).<ref>{{cite web |url=http://www.nybooks.com/articles/2016/10/13/greenhouse-warming-prepare-for-the-worst/ |title=Greenhouse Warming: Prepare for the Worst |author=Laurence C. Smith |date=13 October 2016 |website=nybooks.com |publisher=[[The New York Review of Books]] |access-date=4 October 2016 |quote=review of ''Atmosphere of Hope: Searching for Solutions to the Climate Crisis''}}</ref> Published in the United Kingdom with the title ''Atmosphere of Hope: Solutions to the Climate Crisis'', [[Penguin Books]] ({{ISBN|9780141981048}}).
*Tim Flannery (2017), ''Sunlight and Seaweed: An Argument for How to Feed, Power and Clean Up the World''
*Tim Flannery (2018), ''Europe: A Natural History'', Text Publishing, {{ISBN|9781925603941}}
*Tim Flannery (2019), ''Life: Selected Writings'', Text Publishing, {{ISBN|9781922268297}}
;As editor
*''The Birth of Melbourne'' ({{ISBN|1-877008-89-3}}).
*''The Birth of Sydney'' ({{ISBN|1-876485-45-0}}).
*''The Explorers'' ({{ISBN|1-876485-22-1}}).
*[[Watkin Tench]], ''Watkin Tench's 1788'' ({{ISBN|1-875847-27-8}}).
*''Terra Australis: [[Matthew Flinders]]' Great Adventures in the Circumnavigation of Australia'' ({{ISBN|1-876485-92-2}}).
*John Morgan, ''The Life and Adventures of [[William Buckley (convict)|William Buckley]]'' ({{ISBN|1-877008-20-6}}).
*[[John Nicol]], ''Life and Adventures: 1776–1801'' ({{ISBN|1-875847-41-3}}).
*[[Joshua Slocum]], ''Sailing Alone Around the World'' ({{ISBN|1-877008-57-5}}).
===Articles===
* Tim Flannery, "The First Mean Streets" (review of [[Monica L. Smith]], ''Cities: The First 6,000 Years'', Viking, 2019, 293 pp.; and [[James C. Scott]], ''Against the Grain: A Deep History of the Earliest States'', Yale University Press, 2017, 312 pp.), ''[[The New York Review of Books]]'', vol. LXVII, no. no. 4 (12 March 2020), pp. 31–32. "''Against the Grain'' ... made me look afresh at the [[city|urban world]]. Now when I see monumental architecture, I think of the workers who in many cases literally slaved over its construction. And, having been awakened to the concept, I see cases of near-[[politicide]] everywhere, from the growing [[inequality of wealth]] in our societies, to the taxpayer-funded [[bank bailouts]] following the [[2008 financial crisis]].... Over the millenia the ordinary people of the [[city]] have, with some... success, striven to wrest back control of their lives. But the journey is not yet complete: [[slavery]] continues to exist, and even in our modern [[democracies]] the wealthiest continue to exert vastly disproportionate [[political]] influence. [M]ovements like [[Occupy movement|Occupy]] and [[Extinction Rebellion]] are the latest manifestations of a struggle that is as old as cities themselves." (p. 32.)
==Television series==
*''Two Men in a Tinnie'' (2006) with [[John Doyle (comedian)|John Doyle]]
*''Two in the Top End'' (2008) with John Doyle
*''Two on the Great Divide'' (2012) with John Doyle
*''Two Men in China'' (2014) with John Doyle
==References==
{{reflist|30em}}
== External links ==
{{Wikiquote}}
{{Commons category}}
* [https://web.archive.org/web/20120410045740/http://www.climatecommission.gov.au/ Climate Commission]
* [https://web.archive.org/web/20110222214717/http://www.theweathermakers.org/ ''The Weather Makers'' book website]
* [https://web.archive.org/web/20070830161221/http://www.rmit.edu.au/appliedcommunication/publiclectures Tim Flannery lecture online, RMIT School of Applied Communication Public Lecture series]
* [http://ourworldinbalance.blogspot.com/2007/10/story-of-tim-flannery.html The Story of Tim Flannery] by Our World in Balance
* [http://www.nybooks.com/authors/115 Flannery author page and article archive] from ''[[The New York Review of Books]]'', accessed 6/3/2018
===Video===
* [http://www.themonthly.com.au/tim-flannery Tim Flannery on SlowTV] {{Webarchive|url=https://web.archive.org/web/20140715235911/http://www.themonthly.com.au/tim-flannery |date=15 July 2014 }}
* [https://web.archive.org/web/20080827220629/http://www.twenty.uts.edu.au/streaming/vod-tf Address from Professor Tim Flannery] at ''[[University of Technology, Sydney]]'', recording of live speech, 22 May 2008
* [http://www.cbc.ca/thehour/video.php?id=1502 Tim Flannery interview] on ''[[The Hour (Canadian TV series)|The Hour]]'' with [[George Stroumboulopoulos]]
* [https://web.archive.org/web/20071113202430/http://www.democracynow.org/article.pl?sid=07%2F10%2F25%2F1454240 Tim Flannery interview] on ''[[Democracy Now!]]'' program, 25 October 2007
* [[APEC Singapore 2009]] [http://edition.cnn.com/2009/BUSINESS/11/14/apec.environment/ The business of climate: A look to technology]
* [https://web.archive.org/web/20090815185342/http://worldfuturecouncil.org/tim_flannery.html Councillor at World Future Council]
* [http://wheelercentre.com/videos/video/innovation-in-a-changing-climate/ Keynote address at Alfred Deakin Lecture series "Innovation in Changing Climate"] {{Webarchive|url=https://web.archive.org/web/20140728072431/http://wheelercentre.com/videos/video/innovation-in-a-changing-climate/ |date=28 July 2014 }}
* [https://www.youtube.com/watch?v=7xOVkS11FVs Tim Flannery: Here on Earth (ABC Radio National)], 23 September 2010
{{Australians of the Year}}
{{Authority control}}
{{DEFAULTSORT:Flannery, Tim}}
[[Category:1956 births]]
[[Category:20th-century Australian non-fiction writers]]
[[Category:20th-century Australian scientists]]
[[Category:21st-century Australian non-fiction writers]]
[[Category:21st-century Australian scientists]]
[[Category:Australian activists]]
[[Category:Australian biologists]]
[[Category:Australian environmentalists]]
[[Category:Australian of the Year Award winners]]
[[Category:Australian paleontologists]]
[[Category:Australian science writers]]
[[Category:Australian zoologists]]
[[Category:Climate activists]]
[[Category:Climate change environmentalists]]
[[Category:Cultural critics]]
[[Category:Directors of museums in Australia]]
[[Category:Fellows of the Australian Academy of Science]]
[[Category:Harvard University faculty]]
[[Category:La Trobe University alumni]]
[[Category:Living people]]
[[Category:Macquarie University faculty]]
[[Category:Monash University alumni]]
[[Category:The New York Review of Books people]]
[[Category:Non-fiction environmental writers]]
[[Category:People from Adelaide]]
[[Category:People from Melbourne]]
[[Category:Quarterly Essay people]]
[[Category:Social commentators]]
[[Category:Social critics]]
[[Category:Sustainability advocates]]
[[Category:University of Adelaide faculty]]
[[Category:University of New South Wales alumni]]<noinclude>
<small>This page was moved from [[:en:Tim Flannery]]. Its edit history can be viewed at [[Tim Flannery/edithistory]]</small></noinclude>
3q8ex9i98huibu65or0sw46lm35gexy
Shangri-La Beer
0
114010
737225
690283
2026-04-08T18:13:11Z
Bad-editor-111
62134
AutoModerator Test
737225
wikitext
text/x-wiki
[[Category:Beer in China]]
[[Category:Breweries in China]]
[[Category:Companies of China]]
fipjgxyj0ovweuvb4m8qjjs5t2823k9
Bus
0
116762
737274
735731
2026-04-08T20:20:52Z
AsteraBot
69554
Bot: Adding {{reflist}}
737274
wikitext
text/x-wiki
[[File:Laidlaw school bus.jpg|thumb]]
[[File:Camel Bus in Downtown Havana.jpg|thumb]]
[[File:Broken down bus Westbourne Park Bus Garage 109.jpg|thumb]]
i like buses!
[[File:Bus at Stratford City Bus Station.jpg|thumb]]
[[File:Bendy Bus Aug04.jpg|thumb]]
[[Category:Category of the same colour as a London bus]]
== References ==
{{reflist}}
lze2qkbgjml6xbmhqoxgcjcnokil0ad
MavetunaTest
0
119513
737282
689706
2026-04-08T22:00:32Z
~2026-21834-82
73468
737282
wikitext
text/x-wiki
Test on Jul 21/2025
{{#function:Z18453|asdf}}{{#function:Z18453|asdf}} Function with the wrong Type of arguments:
{{#function:Z18428|23}}{{#function:Z802|Z42|bar|baz}}
== Testing string functions ==
Use '''join''' with two unnamed args:
This is a function surrounded by text {{#function:Z10000|pango|lin2}}
{{#function:Z10000|pango|lin2}}
== renderlang and parselang ==
Use '''join''' with two unnamed args and renderlang/parselang:
This is a template with text: {{Date|12|23|4}} text after the template output
{{#function:Z20773|3 May 2025}}
This is a failed function {{#function:Z10000|pango|lin|renderlang=en|parselang=es||||||||}}
== Testing parsers ==
=== With Natural Number/Z13518: ===
As input: use '''duplicate string N times/Z10911''' function
{{#function:Z12624|ar|3}}
{{#function:Z12624|Wikifunctions|3}}
As input and output: use '''multiply two natural numbers/Z13539''' function
{{#function:Z13539|2|2}}
=== With Float/Z20838: ===
As input: use '''float as percent/Z21000'''
{{#function:Z21000|0.25}}
As input and output: use '''meters to miles/Z18447'''
{{#function:Z18447|10}}
== Testing enums ==
=== With Boolean: ===
Use '''if string output''' true->hello
{{#function:Z11542|Z41|hello|world}}
Use '''if string output''' false->world
{{#function:Z11542|Z42|hello|world}}
=== With Gregorian calendar month: ===
Use '''days in month when not a leap year''':
January has {{#function:Z16316|Z16101}} days
Febuary has {{#function:Z16316|Z16101}} days
==Testing default values==
<NOT YET DONE>
Use '''age''' with filled values:
Geno is {{#function:Z20756|31-05-1985|22-03-2025}} years old
Use '''age''' with empty value:
Someone is {{#function:Z20756|31-05-1985|}} years old
== Testing errors ==
=== Unknown_object ===
{{#function:join|one|two}}
{{#function:Z10000000|one|two}}
=== Not_a_function ===
{{#function:Z11|one|two}}
=== Input_type_not_supported ===
{{#function:Z14329|en}}
=== Output_type_not_supported ===
{{#function:Z860|en}}
=== Invalid_language_properties ===
{{#function:Z10000|foo|bar|renderlang=Z1002|parselang=Z1003||||||||||||||||||||||||||||||||||||||||||||}}
=== Wrong_number_of_inputs ===
{{#function:Z10000|foo|bar|wiii}}
{{#function:Z10000|foo}}
=== Wrong_input_type ===
{{#function:Z802|Z1002|foo|bar}}
=== Failed_evaluation ===
NOTE: to test this, go to the page for Z10000 and disconnect all implementations, and then run:
{{#function:Z10000|one string|another string}}
7mfj81x3nuyq5c9chtg8mt9tqey9c04
John Adams
0
119795
737214
689414
2026-04-08T18:08:11Z
Bad-editor-111
62134
Replaced content with "[[da:John Adams]] [[de:John Adams (US-Präsident)]] [[es:John Adams]] [[eo:John ADAMS]] [[fr:John Adams]] [[he:ג'ון אדאמס]] [[nl:John Adams]] [[ja:ジョン・アダムズ]] [[pl:John Adams]] [[simple:John Adams]] [[fi:John Adams]] [[sv:John Adams]] [[uk:Адамс Джон]] [[zh:约翰·亚当斯]] Cate..."
737214
wikitext
text/x-wiki
[[da:John Adams]]
[[de:John Adams (US-Präsident)]]
[[es:John Adams]]
[[eo:John ADAMS]]
[[fr:John Adams]]
[[he:ג'ון אדאמס]]
[[nl:John Adams]]
[[ja:ジョン・アダムズ]]
[[pl:John Adams]]
[[simple:John Adams]]
[[fi:John Adams]]
[[sv:John Adams]]
[[uk:Адамс Джон]]
[[zh:约翰·亚当斯]]
[[Category:Presidents of the U.S.|Adams, John]]
[[Category:Vice Presidents of the U.S.|Adams, John]]
[[Category:Continental Congressmen|Adams, John]]
[[Category:Members of the Massachusetts House of Representatives|Adams, John]]
[[Category:Adams family|Adams, John]]
[[Category:1735 births|Adams, John]]
[[Category:1826 deaths|Adams, John]]
[[Category:United States Federalist Party|Adams, John]]
s7nupj0efqibcj4kq3ytkzetdu332c1
Moon
0
119986
737215
662613
2026-04-08T18:09:05Z
Bad-editor-111
62134
Replaced content with "*"
737215
wikitext
text/x-wiki
*
q37hcqzkhu1rsia7lbtbi2g7lh7y9gh
Kirchliche Arbeit Alpirsbach
0
121910
737223
552510
2026-04-08T18:12:47Z
Bad-editor-111
62134
737223
wikitext
text/x-wiki
[[Category:Christian organisations based in Germany]]
[[Category:Lutheran liturgy and worship]]
[[Category:German musical groups]]
[[Category:Protestantism in Germany]]
8u0f8dmy36s4nlo20bmkilb0o8zat46
User:SongVĩ.Bot II
2
124239
737207
737084
2026-04-08T17:00:15Z
SongVĩ.Bot II
52414
[[User:SongVĩ.Bot II|Task 0]]: Đã 1563 ngày...
737207
wikitext
text/x-wiki
Cập nhật lần cuối: 09-04-2026
Đã 1563 ngày...
m6rxjcnm6fn8ehafafrsjsw3b1lvn44
REVISIONTIMESTAMP test/1
0
146696
737222
547384
2026-04-08T18:12:03Z
~2026-21529-98
73464
AutoModerator test
737222
wikitext
text/x-wiki
[[REVISIONTIMESTAMP test/1]]:
* current time: {{#time:U}}
* R
8jzuby1jlvgoziow3xc3dopcc8p8vhc
Baboon Mandrill
0
147876
737217
677066
2026-04-08T18:09:53Z
Bad-editor-111
62134
AutoModerator Test
737217
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
Tahoma (typeface)
0
153803
737216
690471
2026-04-08T18:09:30Z
Bad-editor-111
62134
Replaced content with "* [[Category:Humanist sans-serif typefaces]] [[Category:Microsoft typefaces]] [[Category:Windows XP typefaces]] [[de:Tahoma]] [[fr:Tahoma]] [[ko:타호마 (글꼴)]] [[ku:Tahoma]] [[hu:Tahoma]] [[zh:Tahoma]]"
737216
wikitext
text/x-wiki
*
[[Category:Humanist sans-serif typefaces]]
[[Category:Microsoft typefaces]]
[[Category:Windows XP typefaces]]
[[de:Tahoma]]
[[fr:Tahoma]]
[[ko:타호마 (글꼴)]]
[[ku:Tahoma]]
[[hu:Tahoma]]
[[zh:Tahoma]]
emu78jmsbst0br5q3w6kvyjxamwdrt0
TestMoveTalk0Target
0
154102
737228
724009
2026-04-08T18:14:26Z
~2026-21529-98
73464
AutoModerator test
737228
wikitext
text/x-wiki
test
[[Category:Cat L1-Alpha]]
[[Category:Dog L1-Behavior]]
[[Category:Graph Test L1-ID26935]]
pkhfrrlukpv557fk6t4rqe04sndh84a
User talk:JWBTH/CD test page
3
154341
737165
737163
2026-04-08T12:07:59Z
JWBTH
52211
/* Transcluded comments */ edit comment ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737165
wikitext
text/x-wiki
transcluded comment. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:21, 7 April 2026 (UTC)
:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:22, 7 April 2026 (UTC)
::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:23, 7 April 2026 (UTC)
:::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 05:49, 7 April 2026 (UTC)
::::test
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:18, 7 April 2026 (UTC)
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:12, 7 April 2026 (UTC)
: {|
| table
|} [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:35, 7 April 2026 (UTC)
{{text|comment in a template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:30, 7 April 2026 (UTC)}}
:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:31, 7 April 2026 (UTC)
: reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
{{User_talk:JWBTH/CD_test_page/closed|comment in a closed template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:38, 7 April 2026 (UTC)}}
some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text.
{{User_talk:JWBTH/CD_test_page/end closed}}
td074zueipadp5r28sebwkvsprewhpw
737166
737165
2026-04-08T12:08:21Z
JWBTH
52211
Undid revision [[Special:Diff/737165|737165]] by [[Special:Contributions/JWBTH|JWBTH]] ([[User talk:JWBTH|talk]])
737166
wikitext
text/x-wiki
== Section 1 ==
first section comment [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:37, 20 November 2024 (UTC)
unsigned comment
end {{unsigned|user}}
: comment to be edited [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:38, 20 November 2024 (UTC)
:: comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 02:41, 20 November 2024 (UTC)
::: child comment of comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 06:09, 27 August 2025 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
:::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
: [[#c-Test_account_8-20241120023700-Section_1|Test account 8 @ 02:37, 20 November 2024 (UTC)]] [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:43, 28 March 2026 (UTC)
=== test2 ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:55, 14 September 2025 (UTC)
: [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 20:13, 26 March 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:17, 8 April 2026 (UTC)
=== Comment with complex markup ===
* ̴͍͖̪̭̂ฑεᚹẻ̴̦̜̜͙̰̉̒͠͠иℳἒԊ৩βà̸̩̳̗m̶̧̲̲̬̌̀̈́̀ь β ì̵̛̹̌͛͝«Зᾷу៚ἐฑἒдì̵̛̹̌͛͝ю»ì̵̛̹̌͛͝ ! Ᾰ D̴̞̓̊̀ля чẻ̴̦̜̜͙̰̉̒͠͠рẻ̴̦̜̜͙̰̉̒͠͠счуr̵̢͈͕̺͎̀̅ s̸̢̈́ерьӚz̵͓̫̻͔͠Ԋыᚸ βыΔε௭иm̶̧̲̲̬̌̀̈́̀ь <s>ฑр৩c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀раԊc̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀β৩</s> ३ᾷβ৩Δь «D̴̞̓̊̀β৩йԊая z̵͓̫̻͔͠à̸̩̳̗௶พь». Ἇ m̶̧̲̲̬̌̀̈́̀৩ иz̵͓̫̻͔͠ Ԋẻ̴̦̜̜͙̰̉̒͠͠k̸̟͔̯̯̖̍̂͐̎͘৩m̶̧̲̲̬̌̀̈́̀৩ᚹыᚸ c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀ᾷ z̵͓̫̻͔͠а௶พь m̶̧̲̲̬̌̀̈́̀ᾷк ì̵̛̹̌͛͝и ௭ε३εm̶̧̲̲̬̌̀̈́̀ чεᚹعz̵͓̫̻͔͠ k̸̟͔̯̯̖̍̂͐̎͘ᚹᾷй !!! ̴͍͖̪̭̂ <span style="font-family:Calibri; font-size:175%; display: inline-block; letter-spacing: 5px; transform: rotate(10deg); padding: 20px 0px;>[[User:Example|'''<span style="color: Magenta; position: relative; top: -4px;">ঞ</span><span style="color: SpringGreen; position: relative; top: -3px;">ʆ</span><span style="color: red; position: relative; top: -2px;">ἕ</span><span style="color: LimeGreen; position: relative; top: -1px;">ฃ</span><span style="color: DeepPink; position: relative; top: 2px;">r̵̢͈͕̺͎̀̅</span><span style="color: Aqua; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: DarkOrange; position: relative; top: 4px;">D̴̞̓̊̀</span><span style="color: DarkOrchid; position: relative; top: 3px;">ἒ</span><span style="color: Chartreuse; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: Fuchsia; position: relative; top: 1px;">ໃ</span><span style="color: DarkTurquoise; position: relative; top: 0px;">à̸̩̳̗</span><span style="color: Forestgreen; position: relative; top: -2px;">ʁ</span><span style="color: deeppink; position: relative; top: 2px;">i̵͖̒͆̕͝ͅ</span><span style="color: Turquoise; position: relative; top: -1px;">ń̸̳͑̑͌</span><span style="color: LimeGreen; position: relative; top: -4px;">៩</span><span style="color: Magenta; position: relative; top: 1px;">♥</font>''']]</span> 14:08, 1 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:53, 7 April 2026 (UTC)
*:iaos dhioas dhoia sdhiosa d [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:38, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:tests [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:57, 8 April 2026 (UTC)
=== Transcluded comments ===
{{User talk:JWBTH/CD test page/comment}}
=== Last subsection ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:56, 14 September 2025 (UTC)
::::::::::::::: <nowiki>__NOGALLERY__</nowiki> [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 09:24, 31 March 2026 (UTC)
== Section to add test comments ==
section [[User:Example|Example]] ([[User talk:Example|talk]]) 02:37, 1 March 2026 (UTC)
: Test comment with random number 0.08406505844874512 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:32, 16 March 2026 (UTC)
: Test. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:16, 16 March 2026 (UTC)
: Test. test3 [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:17, 16 March 2026 (UTC)
: Test comment with random number 0.8357927622675184 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.47460188540542925 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.687062002939545 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:03, 23 March 2026 (UTC)
: Test comment with random number 0.21500952410025898 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:22, 23 March 2026 (UTC)
: Test comment with random number 0.6571328205265842 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:21, 23 March 2026 (UTC)
: Test comment with random number 0.8725721668943434 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:21, 24 March 2026 (UTC)
: Test comment with random number 0.9535110784110594 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:22, 24 March 2026 (UTC)
: Test comment with random number 0.4330065153484025 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 03:55, 27 March 2026 (UTC)
: Test comment with random number 0.7353033907097808 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.44304195516553146 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.02243804450899023 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.520846091950367 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.9946058761624214 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:25, 27 March 2026 (UTC)
: Test comment with random number 0.1691580237328757 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:28, 27 March 2026 (UTC)
: Test comment with random number 0.06490355868980668 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:30, 27 March 2026 (UTC)
: Test comment with random number 0.9392023221346153 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:32, 27 March 2026 (UTC)
: Test comment with random number 0.5537697536904882 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:38, 1 April 2026 (UTC)
: Test comment with random number 0.470848341599752 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:14, 1 April 2026 (UTC)
: Test comment with random number 0.41673313374066356 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:15, 1 April 2026 (UTC)
: Test comment with random number 0.671732439038764 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:35, 1 April 2026 (UTC)
:testtt [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:10, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 17:59, 7 April 2026 (UTC)
== Section with equals sign (=) for moving ==
<div class="cd-moveMark">''Moved to [[User talk:JWBTH/CD test page 2#Section with equals sign ({{=}}) for moving]]. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 13:40, 1 April 2026 (UTC)''</div>
== Section for moving ==
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 19:52, 15 March 2026 (UTC)
ea8vd1ze34d58mhobd4m53ddz3ykh40
737185
737166
2026-04-08T14:07:22Z
JWBTH
52211
/* Comment with complex markup */ reply to Example: test (-) ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737185
wikitext
text/x-wiki
== Section 1 ==
first section comment [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:37, 20 November 2024 (UTC)
unsigned comment
end {{unsigned|user}}
: comment to be edited [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:38, 20 November 2024 (UTC)
:: comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 02:41, 20 November 2024 (UTC)
::: child comment of comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 06:09, 27 August 2025 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
:::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
: [[#c-Test_account_8-20241120023700-Section_1|Test account 8 @ 02:37, 20 November 2024 (UTC)]] [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:43, 28 March 2026 (UTC)
=== test2 ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:55, 14 September 2025 (UTC)
: [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 20:13, 26 March 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:17, 8 April 2026 (UTC)
=== Comment with complex markup ===
* ̴͍͖̪̭̂ฑεᚹẻ̴̦̜̜͙̰̉̒͠͠иℳἒԊ৩βà̸̩̳̗m̶̧̲̲̬̌̀̈́̀ь β ì̵̛̹̌͛͝«Зᾷу៚ἐฑἒдì̵̛̹̌͛͝ю»ì̵̛̹̌͛͝ ! Ᾰ D̴̞̓̊̀ля чẻ̴̦̜̜͙̰̉̒͠͠рẻ̴̦̜̜͙̰̉̒͠͠счуr̵̢͈͕̺͎̀̅ s̸̢̈́ерьӚz̵͓̫̻͔͠Ԋыᚸ βыΔε௭иm̶̧̲̲̬̌̀̈́̀ь <s>ฑр৩c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀раԊc̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀β৩</s> ३ᾷβ৩Δь «D̴̞̓̊̀β৩йԊая z̵͓̫̻͔͠à̸̩̳̗௶พь». Ἇ m̶̧̲̲̬̌̀̈́̀৩ иz̵͓̫̻͔͠ Ԋẻ̴̦̜̜͙̰̉̒͠͠k̸̟͔̯̯̖̍̂͐̎͘৩m̶̧̲̲̬̌̀̈́̀৩ᚹыᚸ c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀ᾷ z̵͓̫̻͔͠а௶พь m̶̧̲̲̬̌̀̈́̀ᾷк ì̵̛̹̌͛͝и ௭ε३εm̶̧̲̲̬̌̀̈́̀ чεᚹعz̵͓̫̻͔͠ k̸̟͔̯̯̖̍̂͐̎͘ᚹᾷй !!! ̴͍͖̪̭̂ <span style="font-family:Calibri; font-size:175%; display: inline-block; letter-spacing: 5px; transform: rotate(10deg); padding: 20px 0px;>[[User:Example|'''<span style="color: Magenta; position: relative; top: -4px;">ঞ</span><span style="color: SpringGreen; position: relative; top: -3px;">ʆ</span><span style="color: red; position: relative; top: -2px;">ἕ</span><span style="color: LimeGreen; position: relative; top: -1px;">ฃ</span><span style="color: DeepPink; position: relative; top: 2px;">r̵̢͈͕̺͎̀̅</span><span style="color: Aqua; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: DarkOrange; position: relative; top: 4px;">D̴̞̓̊̀</span><span style="color: DarkOrchid; position: relative; top: 3px;">ἒ</span><span style="color: Chartreuse; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: Fuchsia; position: relative; top: 1px;">ໃ</span><span style="color: DarkTurquoise; position: relative; top: 0px;">à̸̩̳̗</span><span style="color: Forestgreen; position: relative; top: -2px;">ʁ</span><span style="color: deeppink; position: relative; top: 2px;">i̵͖̒͆̕͝ͅ</span><span style="color: Turquoise; position: relative; top: -1px;">ń̸̳͑̑͌</span><span style="color: LimeGreen; position: relative; top: -4px;">៩</span><span style="color: Magenta; position: relative; top: 1px;">♥</font>''']]</span> 14:08, 1 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:53, 7 April 2026 (UTC)
*:iaos dhioas dhoia sdhiosa d [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:38, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:tests [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:57, 8 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
=== Transcluded comments ===
{{User talk:JWBTH/CD test page/comment}}
=== Last subsection ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:56, 14 September 2025 (UTC)
::::::::::::::: <nowiki>__NOGALLERY__</nowiki> [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 09:24, 31 March 2026 (UTC)
== Section to add test comments ==
section [[User:Example|Example]] ([[User talk:Example|talk]]) 02:37, 1 March 2026 (UTC)
: Test comment with random number 0.08406505844874512 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:32, 16 March 2026 (UTC)
: Test. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:16, 16 March 2026 (UTC)
: Test. test3 [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:17, 16 March 2026 (UTC)
: Test comment with random number 0.8357927622675184 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.47460188540542925 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.687062002939545 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:03, 23 March 2026 (UTC)
: Test comment with random number 0.21500952410025898 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:22, 23 March 2026 (UTC)
: Test comment with random number 0.6571328205265842 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:21, 23 March 2026 (UTC)
: Test comment with random number 0.8725721668943434 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:21, 24 March 2026 (UTC)
: Test comment with random number 0.9535110784110594 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:22, 24 March 2026 (UTC)
: Test comment with random number 0.4330065153484025 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 03:55, 27 March 2026 (UTC)
: Test comment with random number 0.7353033907097808 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.44304195516553146 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.02243804450899023 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.520846091950367 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.9946058761624214 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:25, 27 March 2026 (UTC)
: Test comment with random number 0.1691580237328757 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:28, 27 March 2026 (UTC)
: Test comment with random number 0.06490355868980668 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:30, 27 March 2026 (UTC)
: Test comment with random number 0.9392023221346153 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:32, 27 March 2026 (UTC)
: Test comment with random number 0.5537697536904882 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:38, 1 April 2026 (UTC)
: Test comment with random number 0.470848341599752 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:14, 1 April 2026 (UTC)
: Test comment with random number 0.41673313374066356 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:15, 1 April 2026 (UTC)
: Test comment with random number 0.671732439038764 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:35, 1 April 2026 (UTC)
:testtt [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:10, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 17:59, 7 April 2026 (UTC)
== Section with equals sign (=) for moving ==
<div class="cd-moveMark">''Moved to [[User talk:JWBTH/CD test page 2#Section with equals sign ({{=}}) for moving]]. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 13:40, 1 April 2026 (UTC)''</div>
== Section for moving ==
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 19:52, 15 March 2026 (UTC)
qkqgucaczvbo3skrd0g3r43u7igusq5
737187
737185
2026-04-08T14:07:53Z
JWBTH
52211
/* Transcluded comments */ edit reply ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737187
wikitext
text/x-wiki
transcluded comment. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:21, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:22, 7 April 2026 (UTC)
::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:23, 7 April 2026 (UTC)
:::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 05:49, 7 April 2026 (UTC)
::::test
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:18, 7 April 2026 (UTC)
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:12, 7 April 2026 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
: {|
| table
|} [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:35, 7 April 2026 (UTC)
{{text|comment in a template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:30, 7 April 2026 (UTC)}}
:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:31, 7 April 2026 (UTC)
: reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
{{User_talk:JWBTH/CD_test_page/closed|comment in a closed template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:38, 7 April 2026 (UTC)}}
some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text.
{{User_talk:JWBTH/CD_test_page/end closed}}
er84z52u27hdimw486gmut5nrafsvia
737188
737187
2026-04-08T14:08:12Z
JWBTH
52211
Undid revision [[Special:Diff/737187|737187]] by [[Special:Contributions/JWBTH|JWBTH]] ([[User talk:JWBTH|talk]])
737188
wikitext
text/x-wiki
== Section 1 ==
first section comment [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:37, 20 November 2024 (UTC)
unsigned comment
end {{unsigned|user}}
: comment to be edited [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:38, 20 November 2024 (UTC)
:: comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 02:41, 20 November 2024 (UTC)
::: child comment of comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 06:09, 27 August 2025 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
:::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
: [[#c-Test_account_8-20241120023700-Section_1|Test account 8 @ 02:37, 20 November 2024 (UTC)]] [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:43, 28 March 2026 (UTC)
=== test2 ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:55, 14 September 2025 (UTC)
: [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 20:13, 26 March 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:17, 8 April 2026 (UTC)
=== Comment with complex markup ===
* ̴͍͖̪̭̂ฑεᚹẻ̴̦̜̜͙̰̉̒͠͠иℳἒԊ৩βà̸̩̳̗m̶̧̲̲̬̌̀̈́̀ь β ì̵̛̹̌͛͝«Зᾷу៚ἐฑἒдì̵̛̹̌͛͝ю»ì̵̛̹̌͛͝ ! Ᾰ D̴̞̓̊̀ля чẻ̴̦̜̜͙̰̉̒͠͠рẻ̴̦̜̜͙̰̉̒͠͠счуr̵̢͈͕̺͎̀̅ s̸̢̈́ерьӚz̵͓̫̻͔͠Ԋыᚸ βыΔε௭иm̶̧̲̲̬̌̀̈́̀ь <s>ฑр৩c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀раԊc̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀β৩</s> ३ᾷβ৩Δь «D̴̞̓̊̀β৩йԊая z̵͓̫̻͔͠à̸̩̳̗௶พь». Ἇ m̶̧̲̲̬̌̀̈́̀৩ иz̵͓̫̻͔͠ Ԋẻ̴̦̜̜͙̰̉̒͠͠k̸̟͔̯̯̖̍̂͐̎͘৩m̶̧̲̲̬̌̀̈́̀৩ᚹыᚸ c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀ᾷ z̵͓̫̻͔͠а௶พь m̶̧̲̲̬̌̀̈́̀ᾷк ì̵̛̹̌͛͝и ௭ε३εm̶̧̲̲̬̌̀̈́̀ чεᚹعz̵͓̫̻͔͠ k̸̟͔̯̯̖̍̂͐̎͘ᚹᾷй !!! ̴͍͖̪̭̂ <span style="font-family:Calibri; font-size:175%; display: inline-block; letter-spacing: 5px; transform: rotate(10deg); padding: 20px 0px;>[[User:Example|'''<span style="color: Magenta; position: relative; top: -4px;">ঞ</span><span style="color: SpringGreen; position: relative; top: -3px;">ʆ</span><span style="color: red; position: relative; top: -2px;">ἕ</span><span style="color: LimeGreen; position: relative; top: -1px;">ฃ</span><span style="color: DeepPink; position: relative; top: 2px;">r̵̢͈͕̺͎̀̅</span><span style="color: Aqua; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: DarkOrange; position: relative; top: 4px;">D̴̞̓̊̀</span><span style="color: DarkOrchid; position: relative; top: 3px;">ἒ</span><span style="color: Chartreuse; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: Fuchsia; position: relative; top: 1px;">ໃ</span><span style="color: DarkTurquoise; position: relative; top: 0px;">à̸̩̳̗</span><span style="color: Forestgreen; position: relative; top: -2px;">ʁ</span><span style="color: deeppink; position: relative; top: 2px;">i̵͖̒͆̕͝ͅ</span><span style="color: Turquoise; position: relative; top: -1px;">ń̸̳͑̑͌</span><span style="color: LimeGreen; position: relative; top: -4px;">៩</span><span style="color: Magenta; position: relative; top: 1px;">♥</font>''']]</span> 14:08, 1 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:53, 7 April 2026 (UTC)
*:iaos dhioas dhoia sdhiosa d [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:38, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:tests [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:57, 8 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
=== Transcluded comments ===
{{User talk:JWBTH/CD test page/comment}}
=== Last subsection ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:56, 14 September 2025 (UTC)
::::::::::::::: <nowiki>__NOGALLERY__</nowiki> [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 09:24, 31 March 2026 (UTC)
== Section to add test comments ==
section [[User:Example|Example]] ([[User talk:Example|talk]]) 02:37, 1 March 2026 (UTC)
: Test comment with random number 0.08406505844874512 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:32, 16 March 2026 (UTC)
: Test. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:16, 16 March 2026 (UTC)
: Test. test3 [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:17, 16 March 2026 (UTC)
: Test comment with random number 0.8357927622675184 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.47460188540542925 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.687062002939545 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:03, 23 March 2026 (UTC)
: Test comment with random number 0.21500952410025898 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:22, 23 March 2026 (UTC)
: Test comment with random number 0.6571328205265842 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:21, 23 March 2026 (UTC)
: Test comment with random number 0.8725721668943434 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:21, 24 March 2026 (UTC)
: Test comment with random number 0.9535110784110594 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:22, 24 March 2026 (UTC)
: Test comment with random number 0.4330065153484025 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 03:55, 27 March 2026 (UTC)
: Test comment with random number 0.7353033907097808 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.44304195516553146 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.02243804450899023 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.520846091950367 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.9946058761624214 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:25, 27 March 2026 (UTC)
: Test comment with random number 0.1691580237328757 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:28, 27 March 2026 (UTC)
: Test comment with random number 0.06490355868980668 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:30, 27 March 2026 (UTC)
: Test comment with random number 0.9392023221346153 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:32, 27 March 2026 (UTC)
: Test comment with random number 0.5537697536904882 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:38, 1 April 2026 (UTC)
: Test comment with random number 0.470848341599752 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:14, 1 April 2026 (UTC)
: Test comment with random number 0.41673313374066356 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:15, 1 April 2026 (UTC)
: Test comment with random number 0.671732439038764 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:35, 1 April 2026 (UTC)
:testtt [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:10, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 17:59, 7 April 2026 (UTC)
== Section with equals sign (=) for moving ==
<div class="cd-moveMark">''Moved to [[User talk:JWBTH/CD test page 2#Section with equals sign ({{=}}) for moving]]. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 13:40, 1 April 2026 (UTC)''</div>
== Section for moving ==
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 19:52, 15 March 2026 (UTC)
qkqgucaczvbo3skrd0g3r43u7igusq5
737189
737188
2026-04-08T14:09:50Z
JWBTH
52211
/* Transcluded comments */ edit addition ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737189
wikitext
text/x-wiki
== Section 1 ==
first section comment [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:37, 20 November 2024 (UTC)
unsigned comment
end {{unsigned|user}}
: comment to be edited [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:38, 20 November 2024 (UTC)
:: comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 02:41, 20 November 2024 (UTC)
::: child comment of comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 06:09, 27 August 2025 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
:::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
: [[#c-Test_account_8-20241120023700-Section_1|Test account 8 @ 02:37, 20 November 2024 (UTC)]] [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:43, 28 March 2026 (UTC)
=== test2 ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:55, 14 September 2025 (UTC)
: [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 20:13, 26 March 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:17, 8 April 2026 (UTC)
=== Comment with complex markup ===
* ̴͍͖̪̭̂ฑεᚹẻ̴̦̜̜͙̰̉̒͠͠иℳἒԊ৩βà̸̩̳̗m̶̧̲̲̬̌̀̈́̀ь β ì̵̛̹̌͛͝«Зᾷу៚ἐฑἒдì̵̛̹̌͛͝ю»ì̵̛̹̌͛͝ ! Ᾰ D̴̞̓̊̀ля чẻ̴̦̜̜͙̰̉̒͠͠рẻ̴̦̜̜͙̰̉̒͠͠счуr̵̢͈͕̺͎̀̅ s̸̢̈́ерьӚz̵͓̫̻͔͠Ԋыᚸ βыΔε௭иm̶̧̲̲̬̌̀̈́̀ь <s>ฑр৩c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀раԊc̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀β৩</s> ३ᾷβ৩Δь «D̴̞̓̊̀β৩йԊая z̵͓̫̻͔͠à̸̩̳̗௶พь». Ἇ m̶̧̲̲̬̌̀̈́̀৩ иz̵͓̫̻͔͠ Ԋẻ̴̦̜̜͙̰̉̒͠͠k̸̟͔̯̯̖̍̂͐̎͘৩m̶̧̲̲̬̌̀̈́̀৩ᚹыᚸ c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀ᾷ z̵͓̫̻͔͠а௶พь m̶̧̲̲̬̌̀̈́̀ᾷк ì̵̛̹̌͛͝и ௭ε३εm̶̧̲̲̬̌̀̈́̀ чεᚹعz̵͓̫̻͔͠ k̸̟͔̯̯̖̍̂͐̎͘ᚹᾷй !!! ̴͍͖̪̭̂ <span style="font-family:Calibri; font-size:175%; display: inline-block; letter-spacing: 5px; transform: rotate(10deg); padding: 20px 0px;>[[User:Example|'''<span style="color: Magenta; position: relative; top: -4px;">ঞ</span><span style="color: SpringGreen; position: relative; top: -3px;">ʆ</span><span style="color: red; position: relative; top: -2px;">ἕ</span><span style="color: LimeGreen; position: relative; top: -1px;">ฃ</span><span style="color: DeepPink; position: relative; top: 2px;">r̵̢͈͕̺͎̀̅</span><span style="color: Aqua; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: DarkOrange; position: relative; top: 4px;">D̴̞̓̊̀</span><span style="color: DarkOrchid; position: relative; top: 3px;">ἒ</span><span style="color: Chartreuse; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: Fuchsia; position: relative; top: 1px;">ໃ</span><span style="color: DarkTurquoise; position: relative; top: 0px;">à̸̩̳̗</span><span style="color: Forestgreen; position: relative; top: -2px;">ʁ</span><span style="color: deeppink; position: relative; top: 2px;">i̵͖̒͆̕͝ͅ</span><span style="color: Turquoise; position: relative; top: -1px;">ń̸̳͑̑͌</span><span style="color: LimeGreen; position: relative; top: -4px;">៩</span><span style="color: Magenta; position: relative; top: 1px;">♥</font>''']]</span> 14:08, 1 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:53, 7 April 2026 (UTC)
*:iaos dhioas dhoia sdhiosa d [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:38, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:tests [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:57, 8 April 2026 (UTC)
*: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
=== Transcluded comments ===
{{User talk:JWBTH/CD test page/comment}}
=== Last subsection ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:56, 14 September 2025 (UTC)
::::::::::::::: <nowiki>__NOGALLERY__</nowiki> [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 09:24, 31 March 2026 (UTC)
== Section to add test comments ==
section [[User:Example|Example]] ([[User talk:Example|talk]]) 02:37, 1 March 2026 (UTC)
: Test comment with random number 0.08406505844874512 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:32, 16 March 2026 (UTC)
: Test. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:16, 16 March 2026 (UTC)
: Test. test3 [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:17, 16 March 2026 (UTC)
: Test comment with random number 0.8357927622675184 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.47460188540542925 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.687062002939545 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:03, 23 March 2026 (UTC)
: Test comment with random number 0.21500952410025898 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:22, 23 March 2026 (UTC)
: Test comment with random number 0.6571328205265842 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:21, 23 March 2026 (UTC)
: Test comment with random number 0.8725721668943434 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:21, 24 March 2026 (UTC)
: Test comment with random number 0.9535110784110594 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:22, 24 March 2026 (UTC)
: Test comment with random number 0.4330065153484025 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 03:55, 27 March 2026 (UTC)
: Test comment with random number 0.7353033907097808 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.44304195516553146 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.02243804450899023 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.520846091950367 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.9946058761624214 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:25, 27 March 2026 (UTC)
: Test comment with random number 0.1691580237328757 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:28, 27 March 2026 (UTC)
: Test comment with random number 0.06490355868980668 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:30, 27 March 2026 (UTC)
: Test comment with random number 0.9392023221346153 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:32, 27 March 2026 (UTC)
: Test comment with random number 0.5537697536904882 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:38, 1 April 2026 (UTC)
: Test comment with random number 0.470848341599752 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:14, 1 April 2026 (UTC)
: Test comment with random number 0.41673313374066356 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:15, 1 April 2026 (UTC)
: Test comment with random number 0.671732439038764 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:35, 1 April 2026 (UTC)
:testtt [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:10, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 17:59, 7 April 2026 (UTC)
== Section with equals sign (=) for moving ==
<div class="cd-moveMark">''Moved to [[User talk:JWBTH/CD test page 2#Section with equals sign ({{=}}) for moving]]. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 13:40, 1 April 2026 (UTC)''</div>
== Section for moving ==
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 19:52, 15 March 2026 (UTC)
gx24eq07izamwrzod9cs5xvjg3cwn6a
737193
737189
2026-04-08T14:39:44Z
JWBTH
52211
/* Comment with complex markup */ reply to Example: s (-) ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737193
wikitext
text/x-wiki
== Section 1 ==
first section comment [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:37, 20 November 2024 (UTC)
unsigned comment
end {{unsigned|user}}
: comment to be edited [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:38, 20 November 2024 (UTC)
:: comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 02:41, 20 November 2024 (UTC)
::: child comment of comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 06:09, 27 August 2025 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
:::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
: [[#c-Test_account_8-20241120023700-Section_1|Test account 8 @ 02:37, 20 November 2024 (UTC)]] [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:43, 28 March 2026 (UTC)
=== test2 ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:55, 14 September 2025 (UTC)
: [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 20:13, 26 March 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:17, 8 April 2026 (UTC)
=== Comment with complex markup ===
* ̴͍͖̪̭̂ฑεᚹẻ̴̦̜̜͙̰̉̒͠͠иℳἒԊ৩βà̸̩̳̗m̶̧̲̲̬̌̀̈́̀ь β ì̵̛̹̌͛͝«Зᾷу៚ἐฑἒдì̵̛̹̌͛͝ю»ì̵̛̹̌͛͝ ! Ᾰ D̴̞̓̊̀ля чẻ̴̦̜̜͙̰̉̒͠͠рẻ̴̦̜̜͙̰̉̒͠͠счуr̵̢͈͕̺͎̀̅ s̸̢̈́ерьӚz̵͓̫̻͔͠Ԋыᚸ βыΔε௭иm̶̧̲̲̬̌̀̈́̀ь <s>ฑр৩c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀раԊc̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀β৩</s> ३ᾷβ৩Δь «D̴̞̓̊̀β৩йԊая z̵͓̫̻͔͠à̸̩̳̗௶พь». Ἇ m̶̧̲̲̬̌̀̈́̀৩ иz̵͓̫̻͔͠ Ԋẻ̴̦̜̜͙̰̉̒͠͠k̸̟͔̯̯̖̍̂͐̎͘৩m̶̧̲̲̬̌̀̈́̀৩ᚹыᚸ c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀ᾷ z̵͓̫̻͔͠а௶พь m̶̧̲̲̬̌̀̈́̀ᾷк ì̵̛̹̌͛͝и ௭ε३εm̶̧̲̲̬̌̀̈́̀ чεᚹعz̵͓̫̻͔͠ k̸̟͔̯̯̖̍̂͐̎͘ᚹᾷй !!! ̴͍͖̪̭̂ <span style="font-family:Calibri; font-size:175%; display: inline-block; letter-spacing: 5px; transform: rotate(10deg); padding: 20px 0px;>[[User:Example|'''<span style="color: Magenta; position: relative; top: -4px;">ঞ</span><span style="color: SpringGreen; position: relative; top: -3px;">ʆ</span><span style="color: red; position: relative; top: -2px;">ἕ</span><span style="color: LimeGreen; position: relative; top: -1px;">ฃ</span><span style="color: DeepPink; position: relative; top: 2px;">r̵̢͈͕̺͎̀̅</span><span style="color: Aqua; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: DarkOrange; position: relative; top: 4px;">D̴̞̓̊̀</span><span style="color: DarkOrchid; position: relative; top: 3px;">ἒ</span><span style="color: Chartreuse; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: Fuchsia; position: relative; top: 1px;">ໃ</span><span style="color: DarkTurquoise; position: relative; top: 0px;">à̸̩̳̗</span><span style="color: Forestgreen; position: relative; top: -2px;">ʁ</span><span style="color: deeppink; position: relative; top: 2px;">i̵͖̒͆̕͝ͅ</span><span style="color: Turquoise; position: relative; top: -1px;">ń̸̳͑̑͌</span><span style="color: LimeGreen; position: relative; top: -4px;">៩</span><span style="color: Magenta; position: relative; top: 1px;">♥</font>''']]</span> 14:08, 1 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:53, 7 April 2026 (UTC)
*:iaos dhioas dhoia sdhiosa d [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:38, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:tests [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:57, 8 April 2026 (UTC)
*: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
*:s [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:39, 8 April 2026 (UTC)
=== Transcluded comments ===
{{User talk:JWBTH/CD test page/comment}}
=== Last subsection ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:56, 14 September 2025 (UTC)
::::::::::::::: <nowiki>__NOGALLERY__</nowiki> [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 09:24, 31 March 2026 (UTC)
== Section to add test comments ==
section [[User:Example|Example]] ([[User talk:Example|talk]]) 02:37, 1 March 2026 (UTC)
: Test comment with random number 0.08406505844874512 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:32, 16 March 2026 (UTC)
: Test. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:16, 16 March 2026 (UTC)
: Test. test3 [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:17, 16 March 2026 (UTC)
: Test comment with random number 0.8357927622675184 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.47460188540542925 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.687062002939545 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:03, 23 March 2026 (UTC)
: Test comment with random number 0.21500952410025898 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:22, 23 March 2026 (UTC)
: Test comment with random number 0.6571328205265842 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:21, 23 March 2026 (UTC)
: Test comment with random number 0.8725721668943434 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:21, 24 March 2026 (UTC)
: Test comment with random number 0.9535110784110594 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:22, 24 March 2026 (UTC)
: Test comment with random number 0.4330065153484025 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 03:55, 27 March 2026 (UTC)
: Test comment with random number 0.7353033907097808 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.44304195516553146 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.02243804450899023 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.520846091950367 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.9946058761624214 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:25, 27 March 2026 (UTC)
: Test comment with random number 0.1691580237328757 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:28, 27 March 2026 (UTC)
: Test comment with random number 0.06490355868980668 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:30, 27 March 2026 (UTC)
: Test comment with random number 0.9392023221346153 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:32, 27 March 2026 (UTC)
: Test comment with random number 0.5537697536904882 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:38, 1 April 2026 (UTC)
: Test comment with random number 0.470848341599752 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:14, 1 April 2026 (UTC)
: Test comment with random number 0.41673313374066356 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:15, 1 April 2026 (UTC)
: Test comment with random number 0.671732439038764 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:35, 1 April 2026 (UTC)
:testtt [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:10, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 17:59, 7 April 2026 (UTC)
== Section with equals sign (=) for moving ==
<div class="cd-moveMark">''Moved to [[User talk:JWBTH/CD test page 2#Section with equals sign ({{=}}) for moving]]. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 13:40, 1 April 2026 (UTC)''</div>
== Section for moving ==
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 19:52, 15 March 2026 (UTC)
jd98qdto3emc8waummkm5ghna1n2nu4
737234
737193
2026-04-08T18:23:44Z
JWBTH
52211
/* Comment with complex markup */ reply to Example: ss (-) ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737234
wikitext
text/x-wiki
== Section 1 ==
first section comment [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:37, 20 November 2024 (UTC)
unsigned comment
end {{unsigned|user}}
: comment to be edited [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:38, 20 November 2024 (UTC)
:: comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 02:41, 20 November 2024 (UTC)
::: child comment of comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 06:09, 27 August 2025 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
:::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
: [[#c-Test_account_8-20241120023700-Section_1|Test account 8 @ 02:37, 20 November 2024 (UTC)]] [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:43, 28 March 2026 (UTC)
=== test2 ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:55, 14 September 2025 (UTC)
: [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 20:13, 26 March 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:17, 8 April 2026 (UTC)
=== Comment with complex markup ===
* ̴͍͖̪̭̂ฑεᚹẻ̴̦̜̜͙̰̉̒͠͠иℳἒԊ৩βà̸̩̳̗m̶̧̲̲̬̌̀̈́̀ь β ì̵̛̹̌͛͝«Зᾷу៚ἐฑἒдì̵̛̹̌͛͝ю»ì̵̛̹̌͛͝ ! Ᾰ D̴̞̓̊̀ля чẻ̴̦̜̜͙̰̉̒͠͠рẻ̴̦̜̜͙̰̉̒͠͠счуr̵̢͈͕̺͎̀̅ s̸̢̈́ерьӚz̵͓̫̻͔͠Ԋыᚸ βыΔε௭иm̶̧̲̲̬̌̀̈́̀ь <s>ฑр৩c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀раԊc̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀β৩</s> ३ᾷβ৩Δь «D̴̞̓̊̀β৩йԊая z̵͓̫̻͔͠à̸̩̳̗௶พь». Ἇ m̶̧̲̲̬̌̀̈́̀৩ иz̵͓̫̻͔͠ Ԋẻ̴̦̜̜͙̰̉̒͠͠k̸̟͔̯̯̖̍̂͐̎͘৩m̶̧̲̲̬̌̀̈́̀৩ᚹыᚸ c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀ᾷ z̵͓̫̻͔͠а௶พь m̶̧̲̲̬̌̀̈́̀ᾷк ì̵̛̹̌͛͝и ௭ε३εm̶̧̲̲̬̌̀̈́̀ чεᚹعz̵͓̫̻͔͠ k̸̟͔̯̯̖̍̂͐̎͘ᚹᾷй !!! ̴͍͖̪̭̂ <span style="font-family:Calibri; font-size:175%; display: inline-block; letter-spacing: 5px; transform: rotate(10deg); padding: 20px 0px;>[[User:Example|'''<span style="color: Magenta; position: relative; top: -4px;">ঞ</span><span style="color: SpringGreen; position: relative; top: -3px;">ʆ</span><span style="color: red; position: relative; top: -2px;">ἕ</span><span style="color: LimeGreen; position: relative; top: -1px;">ฃ</span><span style="color: DeepPink; position: relative; top: 2px;">r̵̢͈͕̺͎̀̅</span><span style="color: Aqua; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: DarkOrange; position: relative; top: 4px;">D̴̞̓̊̀</span><span style="color: DarkOrchid; position: relative; top: 3px;">ἒ</span><span style="color: Chartreuse; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: Fuchsia; position: relative; top: 1px;">ໃ</span><span style="color: DarkTurquoise; position: relative; top: 0px;">à̸̩̳̗</span><span style="color: Forestgreen; position: relative; top: -2px;">ʁ</span><span style="color: deeppink; position: relative; top: 2px;">i̵͖̒͆̕͝ͅ</span><span style="color: Turquoise; position: relative; top: -1px;">ń̸̳͑̑͌</span><span style="color: LimeGreen; position: relative; top: -4px;">៩</span><span style="color: Magenta; position: relative; top: 1px;">♥</font>''']]</span> 14:08, 1 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:53, 7 April 2026 (UTC)
*:iaos dhioas dhoia sdhiosa d [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:38, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:tests [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:57, 8 April 2026 (UTC)
*: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
*:s [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:39, 8 April 2026 (UTC)
*:ss [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:23, 8 April 2026 (UTC)
=== Transcluded comments ===
{{User talk:JWBTH/CD test page/comment}}
=== Last subsection ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:56, 14 September 2025 (UTC)
::::::::::::::: <nowiki>__NOGALLERY__</nowiki> [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 09:24, 31 March 2026 (UTC)
== Section to add test comments ==
section [[User:Example|Example]] ([[User talk:Example|talk]]) 02:37, 1 March 2026 (UTC)
: Test comment with random number 0.08406505844874512 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:32, 16 March 2026 (UTC)
: Test. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:16, 16 March 2026 (UTC)
: Test. test3 [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:17, 16 March 2026 (UTC)
: Test comment with random number 0.8357927622675184 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.47460188540542925 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.687062002939545 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:03, 23 March 2026 (UTC)
: Test comment with random number 0.21500952410025898 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:22, 23 March 2026 (UTC)
: Test comment with random number 0.6571328205265842 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:21, 23 March 2026 (UTC)
: Test comment with random number 0.8725721668943434 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:21, 24 March 2026 (UTC)
: Test comment with random number 0.9535110784110594 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:22, 24 March 2026 (UTC)
: Test comment with random number 0.4330065153484025 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 03:55, 27 March 2026 (UTC)
: Test comment with random number 0.7353033907097808 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.44304195516553146 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.02243804450899023 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.520846091950367 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.9946058761624214 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:25, 27 March 2026 (UTC)
: Test comment with random number 0.1691580237328757 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:28, 27 March 2026 (UTC)
: Test comment with random number 0.06490355868980668 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:30, 27 March 2026 (UTC)
: Test comment with random number 0.9392023221346153 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:32, 27 March 2026 (UTC)
: Test comment with random number 0.5537697536904882 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:38, 1 April 2026 (UTC)
: Test comment with random number 0.470848341599752 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:14, 1 April 2026 (UTC)
: Test comment with random number 0.41673313374066356 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:15, 1 April 2026 (UTC)
: Test comment with random number 0.671732439038764 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:35, 1 April 2026 (UTC)
:testtt [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:10, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 17:59, 7 April 2026 (UTC)
== Section with equals sign (=) for moving ==
<div class="cd-moveMark">''Moved to [[User talk:JWBTH/CD test page 2#Section with equals sign ({{=}}) for moving]]. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 13:40, 1 April 2026 (UTC)''</div>
== Section for moving ==
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 19:52, 15 March 2026 (UTC)
kqm50gw422muej1w496xmtrjuzv9z1v
737306
737234
2026-04-09T06:46:42Z
JWBTH
52211
/* Last subsection */
737306
wikitext
text/x-wiki
== Section 1 ==
first section comment [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:37, 20 November 2024 (UTC)
unsigned comment
end {{unsigned|user}}
: comment to be edited [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 02:38, 20 November 2024 (UTC)
:: comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 02:41, 20 November 2024 (UTC)
::: child comment of comment to test buttons [[User:Jack who built the house|Jack who built the house]] ([[User talk:Jack who built the house|talk]]) 06:09, 27 August 2025 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
:::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 19:32, 16 March 2026 (UTC)
: [[#c-Test_account_8-20241120023700-Section_1|Test account 8 @ 02:37, 20 November 2024 (UTC)]] [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:43, 28 March 2026 (UTC)
=== test2 ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:55, 14 September 2025 (UTC)
: [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 20:13, 26 March 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:17, 8 April 2026 (UTC)
=== Comment with complex markup ===
* ̴͍͖̪̭̂ฑεᚹẻ̴̦̜̜͙̰̉̒͠͠иℳἒԊ৩βà̸̩̳̗m̶̧̲̲̬̌̀̈́̀ь β ì̵̛̹̌͛͝«Зᾷу៚ἐฑἒдì̵̛̹̌͛͝ю»ì̵̛̹̌͛͝ ! Ᾰ D̴̞̓̊̀ля чẻ̴̦̜̜͙̰̉̒͠͠рẻ̴̦̜̜͙̰̉̒͠͠счуr̵̢͈͕̺͎̀̅ s̸̢̈́ерьӚz̵͓̫̻͔͠Ԋыᚸ βыΔε௭иm̶̧̲̲̬̌̀̈́̀ь <s>ฑр৩c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀раԊc̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀β৩</s> ३ᾷβ৩Δь «D̴̞̓̊̀β৩йԊая z̵͓̫̻͔͠à̸̩̳̗௶พь». Ἇ m̶̧̲̲̬̌̀̈́̀৩ иz̵͓̫̻͔͠ Ԋẻ̴̦̜̜͙̰̉̒͠͠k̸̟͔̯̯̖̍̂͐̎͘৩m̶̧̲̲̬̌̀̈́̀৩ᚹыᚸ c̴̨͍͇̪̏̿́̽̕m̶̧̲̲̬̌̀̈́̀ᾷ z̵͓̫̻͔͠а௶พь m̶̧̲̲̬̌̀̈́̀ᾷк ì̵̛̹̌͛͝и ௭ε३εm̶̧̲̲̬̌̀̈́̀ чεᚹعz̵͓̫̻͔͠ k̸̟͔̯̯̖̍̂͐̎͘ᚹᾷй !!! ̴͍͖̪̭̂ <span style="font-family:Calibri; font-size:175%; display: inline-block; letter-spacing: 5px; transform: rotate(10deg); padding: 20px 0px;>[[User:Example|'''<span style="color: Magenta; position: relative; top: -4px;">ঞ</span><span style="color: SpringGreen; position: relative; top: -3px;">ʆ</span><span style="color: red; position: relative; top: -2px;">ἕ</span><span style="color: LimeGreen; position: relative; top: -1px;">ฃ</span><span style="color: DeepPink; position: relative; top: 2px;">r̵̢͈͕̺͎̀̅</span><span style="color: Aqua; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: DarkOrange; position: relative; top: 4px;">D̴̞̓̊̀</span><span style="color: DarkOrchid; position: relative; top: 3px;">ἒ</span><span style="color: Chartreuse; position: relative; top: 4px;"> ̴͍͖̪̭̂</span><span style="color: Fuchsia; position: relative; top: 1px;">ໃ</span><span style="color: DarkTurquoise; position: relative; top: 0px;">à̸̩̳̗</span><span style="color: Forestgreen; position: relative; top: -2px;">ʁ</span><span style="color: deeppink; position: relative; top: 2px;">i̵͖̒͆̕͝ͅ</span><span style="color: Turquoise; position: relative; top: -1px;">ń̸̳͑̑͌</span><span style="color: LimeGreen; position: relative; top: -4px;">៩</span><span style="color: Magenta; position: relative; top: 1px;">♥</font>''']]</span> 14:08, 1 April 2026 (UTC)
*:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:53, 7 April 2026 (UTC)
*:iaos dhioas dhoia sdhiosa d [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:38, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
*:tests [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:57, 8 April 2026 (UTC)
*: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
*:s [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:39, 8 April 2026 (UTC)
*:ss [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:23, 8 April 2026 (UTC)
=== Transcluded comments ===
{{User talk:JWBTH/CD test page/comment}}
=== Vote ===
Comment.
# Vote 1. [[User:Example|Example]] ([[User talk:Example|talk]]) 06:46, 9 April 2026 (UTC)
# Vote 2. [[User:Example|Example]] ([[User talk:Example|talk]]) 06:46, 9 April 2026 (UTC)
=== Last subsection ===
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 14:56, 14 September 2025 (UTC)
::::::::::::::: <nowiki>__NOGALLERY__</nowiki> [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 09:24, 31 March 2026 (UTC)
== Section to add test comments ==
section [[User:Example|Example]] ([[User talk:Example|talk]]) 02:37, 1 March 2026 (UTC)
: Test comment with random number 0.08406505844874512 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:32, 16 March 2026 (UTC)
: Test. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:16, 16 March 2026 (UTC)
: Test. test3 [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:17, 16 March 2026 (UTC)
: Test comment with random number 0.8357927622675184 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.47460188540542925 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 18:53, 16 March 2026 (UTC)
: Test comment with random number 0.687062002939545 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:03, 23 March 2026 (UTC)
: Test comment with random number 0.21500952410025898 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 12:22, 23 March 2026 (UTC)
: Test comment with random number 0.6571328205265842 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:21, 23 March 2026 (UTC)
: Test comment with random number 0.8725721668943434 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:21, 24 March 2026 (UTC)
: Test comment with random number 0.9535110784110594 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 11:22, 24 March 2026 (UTC)
: Test comment with random number 0.4330065153484025 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 03:55, 27 March 2026 (UTC)
: Test comment with random number 0.7353033907097808 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.44304195516553146 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:23, 27 March 2026 (UTC)
: Test comment with random number 0.02243804450899023 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.520846091950367 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:24, 27 March 2026 (UTC)
: Test comment with random number 0.9946058761624214 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:25, 27 March 2026 (UTC)
: Test comment with random number 0.1691580237328757 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:28, 27 March 2026 (UTC)
: Test comment with random number 0.06490355868980668 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:30, 27 March 2026 (UTC)
: Test comment with random number 0.9392023221346153 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 10:32, 27 March 2026 (UTC)
: Test comment with random number 0.5537697536904882 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 16:38, 1 April 2026 (UTC)
: Test comment with random number 0.470848341599752 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:14, 1 April 2026 (UTC)
: Test comment with random number 0.41673313374066356 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:15, 1 April 2026 (UTC)
: Test comment with random number 0.671732439038764 [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 17:35, 1 April 2026 (UTC)
:testtt [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:10, 7 April 2026 (UTC)
: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 17:59, 7 April 2026 (UTC)
== Section with equals sign (=) for moving ==
<div class="cd-moveMark">''Moved to [[User talk:JWBTH/CD test page 2#Section with equals sign ({{=}}) for moving]]. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 13:40, 1 April 2026 (UTC)''</div>
== Section for moving ==
test [[User:Test account 8|Test account 8]] ([[User talk:Test account 8|talk]]) 19:52, 15 March 2026 (UTC)
qz5ayrq0v4drqn42039zkh60opjb7jf
User:Borhan/test.js
2
159779
737196
736860
2026-04-08T15:18:24Z
Borhan
54019
+
737196
javascript
text/javascript
mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Hoo_man/active_sysops.js&action=raw&ctype=text/javascript');
if(typeof(activeSysopsConfig) == 'undefined') activeSysopsConfig = {};
activeSysopsConfig.markWikisGS = true;
4edqr8bc34ea9c7s8mkfy6jacweri7z
737199
737196
2026-04-08T15:28:06Z
Borhan
54019
Blanked the page
737199
javascript
text/javascript
phoiac9h4m842xq45sp7s6u21eteeq1
User:Borhan/common.js
2
161242
737197
736851
2026-04-08T15:22:55Z
Borhan
54019
+
737197
javascript
text/javascript
mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Hoo_man/active_sysops.js&action=raw&ctype=text/javascript');
if(typeof(activeSysopsConfig) == 'undefined') activeSysopsConfig = {};
activeSysopsConfig.markWikisGS = true;
4edqr8bc34ea9c7s8mkfy6jacweri7z
737198
737197
2026-04-08T15:25:48Z
Borhan
54019
737198
javascript
text/javascript
mw.loader.load('https://test.wikipedia.org/w/index.php?title=User:Borhan/test.js&action=raw&ctype=text/javascript');
5bf9qeuxrt5c9ukks5gql0j2gsmv8i3
Giehhsj
0
162404
737224
689140
2026-04-08T18:12:51Z
~2026-21529-98
73464
737224
wikitext
text/x-wiki
([[Www.wikipedia.org template/temp|Battle of Nara]]) [[Www.wikipedia.org template/temp|[13]]]
qtjjmw2wjl0mrksci6gmigvlukjy66u
...
0
162903
737301
688172
2026-04-09T06:14:15Z
PieWriter
72123
Changing redirect target ([[:m:User:PieWriter/BR.js|BR]])
737301
wikitext
text/x-wiki
#REDIRECT [[Teststts]]
t
cwt5ss61mqa0w30af7y4it2kipyxduy
737302
737301
2026-04-09T06:14:33Z
PieWriter
72123
Restored revision 688172 by [[Special:Contributions/DoubleSpaceBot|DoubleSpaceBot]] ([[User talk:DoubleSpaceBot|talk]]) (TwinkleGlobal)
737302
wikitext
text/x-wiki
#redirect [[…]]
t
eilli8fpvtq7vv3jkuup8awpdrypqfo
Example939
0
163040
737280
673665
2026-04-08T21:30:51Z
MKampurath (WMF)
73191
webr vs pv actor
737280
wikitext
text/x-wiki
Test test
webrequest vs pv_actor
73y0sadg3czfaufvkcf3uxp6ag6q841
Nancy Ajram
0
163402
737277
643645
2026-04-08T20:22:13Z
AsteraBot
69554
Bot: Adding {{reflist}}
737277
wikitext
text/x-wiki
'''Nancy Nabil Ajram''' ( {{lang-ar|نانسي نبيل عجرم}} , testing purposes onluy
== References ==
{{reflist}}
njp1m496dsdnra55vegm0gso1sei491
File:Wikitech-2021-blue-large-icon(2).svg
6
166430
737267
737088
2026-04-08T19:46:58Z
Ladsgroup
2217
Ladsgroup uploaded a new version of [[File:Wikitech-2021-blue-large-icon(2).svg]]
659173
wikitext
text/x-wiki
== Licensing ==
{{self|GFDL|cc-by-sa-all|migration=redundant}}
5ln3dh380i59r738tvezfwc817fpsth
MediaWiki:IncidentReportingConfig.json
8
169251
737247
735807
2026-04-08T18:59:25Z
CaptainEek
47282
Test
737247
json
application/json
{
"ReportIncidentE2ETesterUsers": [
"Asilvering",
"Risker",
"Izno",
"L235",
"Sohom Datta",
"CaptainEek",
"Aoidh",
"AntiCompositeNumber",
"HouseBlaster"
],
"ReportIncidentEnabledNamespaces": [],
"ReportIncident_NonEmergency_Doxing": {},
"ReportIncident_NonEmergency_Doxing_HelpMethod": {
"WikiEmailURL": "",
"Email": "",
"OtherURL": "",
"EmailStewards": true
},
"ReportIncident_NonEmergency_Doxing_HideEditURL": "",
"ReportIncident_NonEmergency_Doxing_ShowWarning": true,
"ReportIncident_NonEmergency_HateSpeech": {},
"ReportIncident_NonEmergency_HateSpeech_HelpMethod": {
"ContactAdmin": "",
"Email": "info-en@example.org"
},
"ReportIncident_NonEmergency_Intimidation": {},
"ReportIncident_NonEmergency_Intimidation_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Intimidation_HelpMethod": {
"ContactAdmin": "Wikipedia:Administrators",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_Other": {},
"ReportIncident_NonEmergency_Other_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Other_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_SexualHarassment": {},
"ReportIncident_NonEmergency_SexualHarassment_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_Spam": {},
"ReportIncident_NonEmergency_Spam_HelpMethod": {
"ContactAdmin": "",
"Email": ""
},
"ReportIncident_NonEmergency_Spam_SpamContentURL": "",
"ReportIncident_NonEmergency_Trolling": {},
"ReportIncident_NonEmergency_Trolling_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"$version": "1.1.0"
}
14d4cthkmdns7yvrsymgh43kmtvudid
737249
737247
2026-04-08T19:02:14Z
CaptainEek
47282
Test
737249
json
application/json
{
"ReportIncidentE2ETesterUsers": [
"Asilvering",
"Risker",
"Izno",
"L235",
"Sohom Datta",
"CaptainEek",
"Aoidh",
"AntiCompositeNumber",
"HouseBlaster"
],
"ReportIncidentEnabledNamespaces": [],
"ReportIncident_NonEmergency_Doxing": {},
"ReportIncident_NonEmergency_Doxing_HelpMethod": {
"WikiEmailURL": "",
"Email": "",
"OtherURL": "",
"EmailStewards": true
},
"ReportIncident_NonEmergency_Doxing_HideEditURL": "",
"ReportIncident_NonEmergency_Doxing_ShowWarning": true,
"ReportIncident_NonEmergency_HateSpeech": {},
"ReportIncident_NonEmergency_HateSpeech_HelpMethod": {
"ContactAdmin": "",
"Email": "info-en@example.org"
},
"ReportIncident_NonEmergency_Intimidation": {},
"ReportIncident_NonEmergency_Intimidation_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Intimidation_HelpMethod": {
"ContactAdmin": "Wikipedia:Administrators",
"Email": "info-en@wikimedia.org",
"ContactCommunity": "Wikipedia:Sandbox"
},
"ReportIncident_NonEmergency_Other": {},
"ReportIncident_NonEmergency_Other_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Other_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_SexualHarassment": {},
"ReportIncident_NonEmergency_SexualHarassment_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_Spam": {},
"ReportIncident_NonEmergency_Spam_HelpMethod": {
"ContactAdmin": "",
"Email": ""
},
"ReportIncident_NonEmergency_Spam_SpamContentURL": "",
"ReportIncident_NonEmergency_Trolling": {},
"ReportIncident_NonEmergency_Trolling_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"$version": "1.1.0"
}
ofv3ilvqvnasvxbvzgnmuzy5e6pvyaz
737258
737249
2026-04-08T19:20:00Z
CaptainEek
47282
Enable in Wikipedia Talk namespace for test purposes
737258
json
application/json
{
"ReportIncidentE2ETesterUsers": [
"Asilvering",
"Risker",
"Izno",
"L235",
"Sohom Datta",
"CaptainEek",
"Aoidh",
"AntiCompositeNumber",
"HouseBlaster"
],
"ReportIncidentEnabledNamespaces": [
5
],
"ReportIncident_NonEmergency_Doxing": {},
"ReportIncident_NonEmergency_Doxing_HelpMethod": {
"WikiEmailURL": "",
"Email": "",
"OtherURL": "",
"EmailStewards": true
},
"ReportIncident_NonEmergency_Doxing_HideEditURL": "",
"ReportIncident_NonEmergency_Doxing_ShowWarning": true,
"ReportIncident_NonEmergency_HateSpeech": {},
"ReportIncident_NonEmergency_HateSpeech_HelpMethod": {
"ContactAdmin": "",
"Email": "info-en@example.org"
},
"ReportIncident_NonEmergency_Intimidation": {},
"ReportIncident_NonEmergency_Intimidation_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Intimidation_HelpMethod": {
"ContactAdmin": "Wikipedia:Administrators",
"Email": "info-en@wikimedia.org",
"ContactCommunity": "Wikipedia:Sandbox"
},
"ReportIncident_NonEmergency_Other": {},
"ReportIncident_NonEmergency_Other_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Other_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_SexualHarassment": {},
"ReportIncident_NonEmergency_SexualHarassment_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_Spam": {},
"ReportIncident_NonEmergency_Spam_HelpMethod": {
"ContactAdmin": "",
"Email": ""
},
"ReportIncident_NonEmergency_Spam_SpamContentURL": "",
"ReportIncident_NonEmergency_Trolling": {},
"ReportIncident_NonEmergency_Trolling_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"$version": "1.1.0"
}
elpiujv2wmwbgg3e1zn0f3fs6fjew03
737308
737258
2026-04-09T06:56:32Z
CaptainEek
47282
test
737308
json
application/json
{
"ReportIncidentE2ETesterUsers": [
"Asilvering",
"Risker",
"Izno",
"L235",
"Sohom Datta",
"CaptainEek",
"Aoidh",
"AntiCompositeNumber",
"HouseBlaster"
],
"ReportIncidentEnabledNamespaces": [
5
],
"ReportIncident_NonEmergency_Doxing": {},
"ReportIncident_NonEmergency_Doxing_HelpMethod": {
"WikiEmailURL": "",
"Email": "",
"OtherURL": "",
"EmailStewards": true
},
"ReportIncident_NonEmergency_Doxing_HideEditURL": "",
"ReportIncident_NonEmergency_Doxing_ShowWarning": true,
"ReportIncident_NonEmergency_HateSpeech": {},
"ReportIncident_NonEmergency_HateSpeech_HelpMethod": {
"ContactAdmin": "",
"Email": "info-en@example.org"
},
"ReportIncident_NonEmergency_Intimidation": {},
"ReportIncident_NonEmergency_Intimidation_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Intimidation_HelpMethod": {
"ContactAdmin": "Wikipedia:Administrators",
"Email": "info-en@wikimedia.org",
"ContactCommunity": "Wikipedia:Sandbox"
},
"ReportIncident_NonEmergency_Other": {},
"ReportIncident_NonEmergency_Other_DisputeResolutionURL": "",
"ReportIncident_NonEmergency_Other_HelpMethod": {
"ContactAdmin": "Potato",
"Email": "",
"ContactCommunity": "Cow"
},
"ReportIncident_NonEmergency_SexualHarassment": {},
"ReportIncident_NonEmergency_SexualHarassment_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"ReportIncident_NonEmergency_Spam": {},
"ReportIncident_NonEmergency_Spam_HelpMethod": {
"ContactAdmin": "",
"Email": ""
},
"ReportIncident_NonEmergency_Spam_SpamContentURL": "",
"ReportIncident_NonEmergency_Trolling": {},
"ReportIncident_NonEmergency_Trolling_HelpMethod": {
"ContactAdmin": "",
"Email": "",
"ContactCommunity": ""
},
"$version": "1.1.0"
}
2bkihuf3c61wuliy8u61vokegfgje4e
Test Article
0
169453
737182
691075
2026-04-08T13:25:58Z
~2026-21714-62
73459
737182
wikitext
text/x-wiki
Test article for testing!
j07pene7wd65zysc2b07obw5l41wmxf
Page 2b
0
169725
737209
704039
2026-04-08T17:29:44Z
~2026-21642-26
73463
737209
wikitext
text/x-wiki
9fdbd2dd39b876b30b10d7cd01e1af83
test
9jz5y4r7e06uyi1x8n4fkek2kqjqbl6
User:ToluAyod/Main Page
2
174233
737172
737127
2026-04-08T12:59:22Z
ToluAyod
69650
Updated Main Page via StarterKit tool
737172
wikitext
text/x-wiki
<div style="text-align: center; font-family: 'Linux Libertine', Georgia, Times, serif; margin: 1.5em 0;">
<span style="font-size: 2.3em; line-height: 1.2;">Welcome to {{SITENAME}} Wikipedia</span><br/>
<span style="font-size: 1.1em; color: #54595d;">The free encyclopedia that anyone can edit</span><br/>
<span style="font-size: 0.95em; color: #72777d; margin-top: 0.5em; display: inline-block;">{{NUMBEROFACTIVEUSERS}} active editors • '''{{NUMBEROFARTICLES}}''' articles in {{SITENAME}}</span>
</div>
{{User:ToluAyod/Starter kit/Content categories}}
<div style="display: flex; gap: 4px; align-items: stretch;"><div style="flex: 1; min-width: 0; display: flex; flex-direction: column;">{{User:ToluAyod/Starter kit/Featured article}}</div><div style="flex: 1; min-width: 0; display: flex; flex-direction: column;">{{User:ToluAyod/Starter kit/Ninu Iroyin}}</div></div>
<div style="display: flex; gap: 4px; align-items: stretch;"><div style="flex: 1; min-width: 0; display: flex; flex-direction: column;">{{User:ToluAyod/Starter kit/Se o mo}}</div><div style="flex: 1; min-width: 0; display: flex; flex-direction: column;">{{User:ToluAyod/Starter kit/Lo jo Oni}}</div></div>
<div style="margin-top: 10px;">{{User:ToluAyod/Starter kit/Featured picture}}</div>
<div style="display: flex; gap: 4px; align-items: stretch; margin-top: 10px;"><div style="flex: 1; min-width: 0; display: flex; flex-direction: column;">{{User:ToluAyod/Starter kit/Community resources}}</div><div style="flex: 1; min-width: 0; display: flex; flex-direction: column;">{{User:ToluAyod/Starter kit/Translation resources}}</div></div>
<div style="margin-top: 10px;">{{User:ToluAyod/Starter kit/Wikipedia's sister projects}}</div>
<div style="margin-top: 10px;">{{User:ToluAyod/Starter kit/Ede Wikipedia}}</div>
__NOTOC__
k799jb8hgx5cocu09a8d1ke1nlbebcw
737285
737172
2026-04-08T22:14:55Z
ToluAyod
69650
Updated Main Page via StarterKit tool
737285
wikitext
text/x-wiki
{{User:ToluAyod/Starter kit/Welcome banner}}
{{User:ToluAyod/Starter kit/Content categories}}
{{User:ToluAyod/Starter kit/In the news}}
{{User:ToluAyod/Starter kit/On this day}}
<div style="margin-top: 10px;">{{User:ToluAyod/Starter kit/Wikipedia's sister projects}}</div>
<div style="margin-top: 10px;">{{User:ToluAyod/Starter kit/Wikipedia languages}}</div>
__NOTOC__
cds57t9hmyspcz4gq9jxliv04k2lsdt
737286
737285
2026-04-08T22:27:44Z
ToluAyod
69650
Updated Main Page via StarterKit tool
737286
wikitext
text/x-wiki
{{User:ToluAyod/Starter kit/Welcome banner}}
{{User:ToluAyod/Starter kit/Content categories}}
{{User:ToluAyod/Starter kit/Ninu iroyin}}
{{User:ToluAyod/Starter kit/Lo jo oni}}
<div style="margin-top: 10px;">{{User:ToluAyod/Starter kit/Wikipedia's sister projects}}</div>
<div style="margin-top: 10px;">{{User:ToluAyod/Starter kit/Ede wikipedia}}</div>
__NOTOC__
co5v2vpewwdqol9oul6ppa58bs2rayw
MediaWiki:AutoModeratorMultilingualConfig.json
8
174236
737218
734070
2026-04-08T18:10:39Z
SCardenas (WMF)
47441
737218
json
application/json
{
"AutoModeratorMultilingualConfigCautionLevel": "less-cautious",
"AutoModeratorMultilingualConfigConfigureThreshold": {
"scalar": ""
},
"AutoModeratorMultilingualConfigEnableBotFlag": false,
"AutoModeratorMultilingualConfigEnableLanguageAgnostic": true,
"AutoModeratorMultilingualConfigEnableMultilingual": false,
"AutoModeratorMultilingualConfigEnableRevisionCheck": true,
"AutoModeratorMultilingualConfigEnableUserRevertsPerPage": false,
"AutoModeratorMultilingualConfigFalsePositivePageTitle": "",
"AutoModeratorMultilingualConfigHelpPageLink": "",
"AutoModeratorMultilingualConfigMultilingualThreshold": "",
"AutoModeratorMultilingualConfigRevertTalkPageMessageEnabled": false,
"AutoModeratorMultilingualConfigRevertTalkPageMessageRegisteredUsersOnly": false,
"AutoModeratorMultilingualConfigSkipUserRights": [
"bot",
"autopatrol"
],
"AutoModeratorMultilingualConfigUseEditFlagMinor": false,
"AutoModeratorMultilingualConfigUserRevertsPerPage": "",
"$version": "1.0.0"
}
j4b5bxopqjt9oxob0reyydbim45d8vj
User:MMunyoki (WMF)/Starter kit/Content categories
2
174262
737204
736109
2026-04-08T16:35:36Z
MMunyoki (WMF)
53374
Created by StarterKit tool
737204
wikitext
text/x-wiki
<div style="margin:10px 0;box-shadow:0 1px 1px rgba(0,0,0,0.1);background:#fff;">
{| style="border-spacing:1px;border-collapse:separate;width:100%;text-align:center;font-size:0.9em;padding:2px 3px;" class="hp-portalen"
| style="background:#F9F9F0;border-top:5px solid #999933;padding:3px 0.25em;width:20%;text-align:center;" | Science & Technology
| style="background:#F4F9F0;border-top:5px solid #669933;padding:3px 0.25em;width:20%;text-align:center;" | History & Events
| style="background:#F0F9F9;border-top:5px solid #339999;padding:3px 0.25em;width:20%;text-align:center;" | Requested Articles
| style="background:#F9F0F9;border-top:5px solid #993399;padding:3px 0.25em;width:20%;text-align:center;" | Countries & Geography
| style="background:#F9F0F0;border-top:5px solid #993333;padding:3px 0.25em;width:20%;text-align:center;" | Arts & Literature
|}
</div>
<noinclude>[[Category:Starter Kit templates]][[Category:Main page templates]]</noinclude>
bwwexy0s9htdxwg4uhqhe3bdbjhhfej
User:PieAlt/common.js
2
174351
737315
736843
2026-04-09T09:01:53Z
PieAlt
73198
Installing [[User:PieAlt/test3.js]] ([[mw:User:Iniquity/scriptManager.js|Script Manager]])
737315
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.api', 'oojs-ui', 'oojs-ui-windows'], function () {
var api = new mw.Api();
function openDialog(username) {
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
function Dialog(config) {
Dialog.super.call(this, config);
}
OO.inheritClass(Dialog, OO.ui.ProcessDialog);
Dialog.static.name = 'globalUserInfoDialog';
Dialog.static.title = 'Global user info: ' + username;
Dialog.static.actions = [
{ action: 'close', label: 'Close', flags: 'safe' }
];
Dialog.prototype.initialize = function () {
Dialog.super.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.$body.append(this.panel.$element);
this.panel.$element.html('Loading global data...');
};
Dialog.prototype.getActionProcess = function (action) {
if (action === 'close') {
return new OO.ui.Process(() => this.close());
}
return Dialog.super.prototype.getActionProcess.call(this, action);
};
var dialog = new Dialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
api.get({
action: 'query',
meta: 'globaluserinfo',
guiuser: username,
guiprop: 'groups|merged|editcount|registration|home'
}).then(function (data) {
if (!data.query || !data.query.globaluserinfo) {
dialog.panel.$element.html('User not found.');
return;
}
var user = data.query.globaluserinfo;
var html = '<b>Username:</b> ' + username + '<br>' +
'<b>Global edits:</b> ' + user.editcount + '<br>' +
'<b>Registered:</b> ' + (user.registration || 'Unknown') + '<br>' +
'<b>Home wiki:</b> ' + (user.home || 'Unknown') + '<br><br>';
html += '<b>Active wikis (top):</b><ul>';
if (user.merged) {
var wikis = user.merged
.filter(w => w.editcount > 0)
.sort((a, b) => b.editcount - a.editcount)
.slice(0, 15);
wikis.forEach(function (wiki) {
html += '<li>' +
wiki.wiki +
' (' + wiki.editcount + ')' +
(wiki.groups && wiki.groups.length ? ' [' + wiki.groups.join(', ') + ']' : '') +
'</li>';
});
if (user.merged.length > 15) {
html += '<li>...and ' + (user.merged.length - 15) + ' more wikis</li>';
}
}
html += '</ul>';
dialog.panel.$element.html(html);
}).catch(function () {
dialog.panel.$element.html('Failed to load global info.');
});
}
function addButtons() {
document.querySelectorAll('a.mw-userlink').forEach(function (link) {
if (link.dataset.globalInfoAdded) return;
link.dataset.globalInfoAdded = '1';
var btn = document.createElement('span');
btn.textContent = ' ⓘ';
btn.style.cursor = 'pointer';
btn.style.color = '#36c';
btn.style.marginLeft = '4px';
btn.addEventListener('click', function (e) {
e.preventDefault();
openDialog(link.textContent.trim());
});
link.after(btn);
});
}
mw.hook('wikipage.content').add(addButtons);
});
// </nowiki>
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:PieAlt/test2.js&action=raw&ctype=text/javascript'); // Backlink: [[User:PieAlt/test2.js]]
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:PieAlt/test3.js&action=raw&ctype=text/javascript'); // Backlink: [[User:PieAlt/test3.js]]
2mh5o1qcyz3ugkocendmnkaxngg0c9t
1956-57 Austrian football championship
0
174424
737273
736662
2026-04-08T20:20:28Z
AsteraBot
69554
Bot: Adding {{reflist}}
737273
wikitext
text/x-wiki
{{Infobox football league season
| competition = [[Austrian Bundesliga|Staatsliga A]]
| season = 1956-57
| dates = 25 August 1956 - 20 June 1957
| winners = [[SK Rapid Wien]]
| relegated = [[FC Stadlau]], [[SV Austria Salzburg]]
|promoted = [[ÖMV Olympia Wien ]], [[FC Wien]]
| matches =
| total goals = 833 (4,58 per match)
| league topscorer = [[Robert Dienst ]] SK Rapid Wien (32 goals)
| prevseason = [[1955-56 Austrian football championship|19555-56]]
| nextseason = [[1957-58 Austrian football championship|1957-58]]
}}
The '''1956-57 Austrian football championship''' was organized was organized by the [[Austrian Football Association]]. The Staatsliga B served as the lower tier of the Staatsliga A. It only included clubs from [[Vienna]], [[Lower Austria]], [[Burgenland]], [[Upper Austria]] and [[Styria]]. For clubs from western Austria the Tauernliga Nord ([[Salzburgerland|Salzburg]]), the Tauernliga Süd ([[Carinthia]]) and the Arlbergliga ([[Tyrol (state)|Tyrol]], [[Vorarlberg]]) were organized. This leagues were Amateur leagues. The respective regional leagues of the federal states served as the third level.
The championship in the State League was contested by 14 teams. They met twice throughout the season. [[SK Rapid Wien|Rapid Wien]] won the Austrian football championship for the 21st time . [[SC Olympia Wien]] as champion of the Staatsliga B and [[FC Wien]] after relegationmatches were promoted to the Staatsliga A for the 1957-58 season.
==Final table==
Table mode (in case of equal points): 1st criterion goal quotient.
{| class="wikitable"
|-
!Pl.
! Name
!M
! W
! D
! L
! P
|-
|1.
|'''SK Rapid Wien'''
|26
| 19
|2
|5
| 40
|-
|2.
|'''First Vienna FC'''
|26
| 17
|5
| 4
| 39
|-
|3.
|'''FK Austria Wien'''
|26
| 18
|2
| 6
|38
|-
|4.
|'''SC Wacker Wien'''
|26
| 14
|7
| 5
| 35
|-
|5.
|'''1. Simmeringer SC'''
|26
| 11
|9
|6
|31
|-
|6.
|'''Grazer AK'''
|26
| 12
|2
| 12
| 26
|-
|7.
|'''Kapfenberger SV'''
|26
| 11
|3
| 12
| 25
|-
|8.
|'''SK Admira Wien'''
|26
| 9
|4
|13
| 22<ref>0,91</ref>
|-
|9.
| '''Kremser SC'''
|26
| 9
|4
| 13
| 22<ref>0,67</ref>
|-
|10.
|'''Wiener Sport-Club'''
|26
|9
|3
| 14
| 21<ref>1,09</ref>
|-
|11.
|'''Wiener AC'''
|26
|8
|5
| 13
| 21<ref>0,94</ref>
|-
|12.
|'''SK Sturm Graz'''
|26
|9
|3
| 14
| 21<ref>0,73</ref>
|-
|13.
|'''SV Austria Salzburg'''
|26
|6
|3
| 17
| 15
|-
|14.
|'''FC Stadlau'''
|26
|2
|4
| 20
| 8
|}
<ref name="ref1">[http://www.austriasoccer.at/data/nat/1950_59/o1__staatsliga_a__1956_57.htm Austria Soccer] </ref>
==Matches ==
<ref name="ref1"/>
===Round 1===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 25 August 1956
|SK Rapid Wien
|1. Simmeringer SC
|1-1
|-
| 26 August 1956
|'''SV Austria Salzburg'''
|Grazer AK
| 3-1
|-
| 26 August 1956
| FC Stadlau
|'''SC Wacker Wien'''
|0-2
|-
| 26 August 1956
|Kremser SC
| SK Admira Wien
|1-1
|-
| 26 August 1956
| '''SK Sturm Graz'''
|Wiener Sport-Club
|3-1
|-
| 26 August 1956
| '''First Vienna FC'''
|KapfenbergerSV
|5-2
|-
| 29 August 1956
| '''FK Austria Wien '''
|Wiener AC
|3-1
|}
===Round 2===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 1 September 1956
|'''1. Simmeringer SC '''
|FC Stadlau
|6-4
|-
| 1 September 1956
| SK Admira Wien
|'''SK Rapid Wien '''
| 1-2
|-
| 2 September 1956
| SV Austria Salzburg
| '''First Vienna FC'''
|0-2
|-
| 2 September 1956
|'''Grazer AK'''
| Wiener Sport-Club
|2-1
|-
| 2 September 1956
| KapfenbergerSV
|'''FK Austria Wien '''
|2-5
|-
| 2 September 1956
| '''SC Wacker Wien'''
|SK Sturm Graz
|7-0
|-
| 2 September 1956
| Wiener AC
|Kremser SC
|2-2
|}
===Round 3===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 5 September 1956
|'''FC Stadlau '''
|SK Admira Wien
|3-2
|-
| 5 September 1956
| Kremser SC
| ''' Kapfenberger SV '''
| 0-6
|-
| 5 September 1956
|'''SK Rapid Wien '''
| Wiener AC
|7-4
|-
| 5 September 1956
|SK Sturm Graz
|'''1. Simmeringer SC '''
|3-4
|-
| 5 September 1956
|Wiener Sport-Club
|SC Wacker Wien
|1-1
|-
| 12 September 1956
|'''FK Austria Wien '''
|SV Austria Salzburg
|4-2
|-
| 12 September 1956
| '''First Vienna FC'''
|Grazer AK
|1-0
|}
===Round 4===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 30 August 1956
|Grazer AK
|'''SC Wacker Wien'''
|1-2
|-
| 8 September 1956
|1. Simmeringer SC
|Wiener Sport-Club
| 2-2
|-
| 8 September 1956
|'''Wiener AC'''
| FC Stadlau
|4-0
|-
| 5 September 1956
|'''SK Admira Wien '''
|SK Sturm Graz
|3-2
|-
| 5 September 1956
|'''SV Austria Salzburg'''
|Kremser SC
|8-3
|-
| 9 September 1956
|''' KapfenbergerSV '''
|SK Rapid Wien
|4-3
|-
| 9 September 1956
| First Vienna FC
|FK Austria Wien
|3-3
|}
===Round 5===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 15 September 1956
|'''SK Rapid Wien '''
|SV Austria Salzburg
|6-1
|-
| 15 September 1956
|Wiener Sport-Club
|'''SK Admira Wien '''
| 0-2
|-
| 16 September 1956
|Kremser SC
| First Vienna FC
|0-1
|-
| 16 September 1956
|FK Austria Wien
|Grazer AK
|4-0
|-
| 16 September 1956
|FC Stadlau
|''' KapfenbergerSV '''
|0-5
|-
| 16 September 1956
|'''SK Sturm Graz '''
|Wiener AC
|4-0
|-
| 16 September 1956
| '''SC Wacker Wien'''
|1. Simmeringer SC
|3-0
|}
===Round 6===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 22 September 1956
| '''SK Admira Wien '''
|SC Wacker Wien
|6-1
|-
| 22 September 1956
|'''Wiener AC'''
|Wiener Sport-Club
| 3-2
|-
| 23 September 1956
|'''SV Austria Salzburg'''
| FC Stadlau
|5-0
|-
| 23 September 1956
|FK Austria Wien
|'''Kremser SC '''
|0-2
|-
| 23 September 1956
| '''Grazer AK '''
|1. Simmeringer SC
|4-0
|-
| 23 September 1956
|''' Kapfenberger SV '''
|SK Sturm Graz
|2-0
|-
| 23 September 1956
| '''First Vienna FC '''
|SK Rapid Wien
|4-1
|}
===Round 7===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 7 October 1956
| ''' 1. Simmeringer SC '''
|SK Admira Wien
|4-0
|-
| 7 October 1956
| '''Kremser SC '''
|Grazer AK
| 2-0
|-
| 7 October 1956
| '''SK Rapid Wien'''
|FK Austria Wien
|4-1
|-
| 7 October 1956
|SK Sturm Graz
|SV Austria Salzburg
|0-0
|-
| 7 October 1956
| '''SC Wacker Wien '''
|Wiener AC
|4-0
|-
| 7 October 1956
|'''Wiener Sport-Club '''
| Kapfenberger SV
|1-0
|-
| 24 October 1956
| FC Stadlau
|First Vienna FC
|1-1
|}
===Round 8===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 20 October 1956
| Wiener AC
|''' 1. Simmeringer SC '''
|1-4
|-
| 21 October 1956
|'''SV Austria Salzburg'''
|Wiener Sport-Club
| 2-1
|-
| 21 October 1956
|'''FK Austria Wien '''
|FC Stadlau
|4-0
|-
| 21 October 1956
|'''Grazer AK '''
|SK Admira Wien
|2-0
|-
| 21 October 1956
| Kapfenberger SV
|'''SC Wacker Wien '''
|0-2
|-
| 21 October 1956
|Kremser SC
| '''SK Rapid Wien '''
|0-2
|-
| 21 October 1956
| '''First Vienna FC'''
|SK Sturm Graz
|4-0
|}
===Round 9===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 27 October 1956
|''' 1. Simmeringer SC '''
|Kapfenberger SV
|4-1
|-
| 27 October 1956
| '''SK Admira Wien '''
|Wiener AC
| 5-2
|-
| 27 October 1956
|SK Rapid Wien
|Grazer AK
|1-1
|-
| 28 October 1956
|SK Sturm Graz
|'''FK Austria Wien '''
|1-3
|-
| 1 November 1956
| '''FC Stadlau '''
| Kremser SC
|3-2
|-
| 7 November 1956
|'''First Vienna FC'''
| Wiener Sport-Club
|4-2
|-
| 7 November 1956
| '''SC Wacker Wien '''
|SV Austria Salzburg
|5-2
|}
===Round 10===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 3 November 1956
| '''FK Austria Wien '''
|Wiener Sport-Club
|1-0
|-
| 3 November 1956
|First Vienna FC
|SC Wacker Wien
| 1-1
|-
| 4 November 1956
|SV Austria Salzburg
|''' 1. Simmeringer SC '''
|1-2
|-
| 4 November 1956
|'''Grazer AK '''
|Wiener AC
|1-0
|-
| 4 November 1956
|'''Kremser SC '''
|SK Sturm Graz
|4-1
|-
| 4 November 1956
|'''SK Rapid Wien '''
| FC Stadlau
|4-2
|-
| 7 November 1956
| ''' Kapfenberger SV '''
|SK Admira Wien
|3-2
|}
===Round 11===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 10 November 1956
| 1. Simmeringer SC
|First Vienna FC
|2-2
|-
| 10 November 1956
|'''SK Sturm Graz '''
|SK Rapid Wien
| 1-0
|-
| 11 November 1956
|'''SK Admira Wien '''
|SV Austria Salzburg
|5-1
|-
| 11 November 1956
|FC Stadlau
|'''Grazer AK '''
|0-2
|-
| 11 November 1956
|SC Wacker Wien
|'''FK Austria Wien '''
|1-2
|-
| 11 November 1956
|'''Wiener AC '''
| Kapfenberger SV
|4-2
|-
| 11 November 1956
| ''' Wiener Sport-Club '''
|Kremser SC
|5-0
|}
===Round 12===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 18 November 1956
|SV Austria Salzburg
|'''Wiener AC '''
|2-3
|-
| 18 November 1956
| '''FK Austria Wien '''
|1. Simmeringer SC
| 3-2
|-
| 18 November 1956
|FC Stadlau
|SK Sturm Graz
|0-0
|-
| 18 November 1956
| Grazer AK
|Kapfenberger SV
|1-1
|-
| 18 November 1956
|Kremser SC
|'''SC Wacker Wien '''
|1-5
|-
| 18 November 1956
|'''SK Rapid Wien'''
| Wiener Sport-Club
|3-1
|-
| 18 November 1956
| ''' First Vienna FC '''
|SK Admira Wien
|3-0
|}
===Round 13===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 24 November 1956
|'''1. Simmeringer SC '''
|Kremser SC
|4-2
|-
| 24 November 1956
|Wiener AC
|''' First Vienna FC '''
| 0-1
|-
| 25 November 1956
|SK Admira Wien
|'''FK Austria Wien '''
|2-6
|-
| 25 November 1956
|'''Kapfenberger SV'''
|SV Austria Salzburg
|3-2
|-
| 25 November 1956
|SK Sturm Graz
| '''Grazer AK '''
|1-6
|-
| 25 November 1956
|'''SC Wacker Wien '''
|SK Rapid Wien
|1-0
|-
| 25 November 1956
| '''Wiener Sport-Club '''
|FC Stadlau
|2-0
|}
===Round 14===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 2 December 1956
| 1. Simmeringer SC
|'''SK Rapid Wien '''
|1-2
|-
| 2 December 1956
|SK Admira Wien
|'''Kremser SC '''
| 0-2
|-
| 2 December 1956
|'''Grazer AK '''
|SV Austria Salzburg
|2-1
|-
| 2 December 1956
|Kapfenberger SV
|''' First Vienna FC'''
|2-4
|-
| 2 December 1956
|'''SC Wacker Wien '''
| FC Stadlau
|5-1
|-
| 2 December 1956
|Wiener AC
|'''FK Austria Wien '''
|3-4
|-
| 2 December 1956
| '''Wiener Sport-Club '''
|SK Sturm Graz
|1-0
|}
===Round 15===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 16 March 1957
|'''SK Rapid Wien '''
|SK Admira Wien
|6-2
|-
| 17 March 1957
| '''FK Austria Wien '''
|Kapfenberger SV
| 2-0
|-
| 17 March 1957
|FC Stadlau
|'''1. Simmeringer SC '''
|2-3
|-
| 17 March 1957
|'''Kremser SC '''
|Wiener AC
|3-0
|-
| 17 March 1957
|SK Sturm Graz
| SC Wacker Wien
|2-2
|-
| 17 March 1957
|''' First Vienna FC'''
| SV Austria Salzburg
|8-3
|-
| 17 March 1957
| '''Wiener Sport-Club '''
|Grazer AK
|8-2
|}
===Round 16===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 23 March 1957
|'''1. Simmeringer SC '''
|SK Sturm Graz
|5-3
|-
| 23 March 1957
|''' SK Admira Wien '''
|FC Stadlau
| 3-0
|-
| 23 March 1957
| SC Wacker Wien
|'''Wiener Sport-Club '''
|0-3
|-
| 24 March 1957
|Kapfenberger SV
|Kremser SC
|2-2
|-
| 24 March 1957
| SV Austria Salzburg
| '''FK Austria Wien '''
|0-2
|-
| 24 March 1957
|''' Grazer AK '''
| First Vienna FC
|2-1
|-
| 24 March 1957
| Wiener AC
| '''SK Rapid Wien '''
|1-2
|}
===Round 17===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 30 March 1957
|'''SK Rapid Wien '''
|Kapfenberger SV
|8-1
|-
| 30 March 1957
|Wiener Sport-Club
|1. Simmeringer SC
| 2-2
|-
| 31 March 1957
| FK Austria Wien
|'''First Vienna FC '''
|0-3
|-
| 31 March 1957
|FC Stadlau
|Wiener AC
|1-1
|-
| 31 March 1957
| ''' Kremser SC '''
| SV Austria Salzburg
|3-1
|-
| 31 March 1957
|'''SK Sturm Graz'''
| SK Admira Wien
|5-3
|-
| 31 March 1957
| '''SC Wacker Wien'''
| Grazer AK
|4-0
|}
===Round 18===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 6 April 1957
|'''Wiener AC '''
|SK Sturm Graz
|2-0
|-
| 7 April 1957
|'''1. Simmeringer SC '''
|SC Wacker Wien
| 3-2
|-
| 7 April 1957
|SK Admira Wien
| '''Wiener Sport-Club '''
|2-3
|-
| 7 April 1957
|SV Austria Salzburg
|'''SK Rapid Wien '''
|4-5
|-
| 7 April 1957
| Grazer AK
| '''FK Austria Wien'''
|0-2
|-
| 7 April 1957
|'''Kapfenberger SV '''
| FC Stadlau
|4-1
|-
| 7 April 1957
| '''First Vienna FC '''
| Kremser SC
|6-2
|}
===Round 19===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 27 April 1957
|'''1. Simmeringer SC '''
|Grazer AK
|3-2
|-
| 28 April 1957
| FC Stadlau
|SV Austria Salzburg
| 3-3
|-
| 28 April 1957
|SC Wacker Wien
|SK Admira Wien
|2-2
|-
| 28 April 1957
|'''Kremser SC '''
|FK Austria Wien
|4-3
|-
| 28 April 1957
| '''SK Rapid Wien '''
| First Vienna FC
|3-2
|-
| 28 April 1957
|'''SK Sturm Graz '''
|Kapfenberger SV
|4-2
|-
| 28 April 1957
| Wiener Sport-Club
| '''Wiener AC '''
|2-6
|}
===Round 20===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 11 May 1957
|SK Admira Wien
|1. Simmeringer SC
|2-2
|-
| 11 May 1957
|Wiener AC
|SC Wacker Wien
|2-2
|-
| 12 May 1957
|SV Austria Salzburg
| '''SK Sturm Graz '''
|1-2
|-
| 12 May 1957
|FK Austria Wien
| '''SK Rapid Wien '''
|2-3
|-
| 12 May 1957
| '''Grazer AK '''
| Kremser SC
|3-2
|-
| 12 May 1957
|'''Kapfenberger SV'''
|Wiener Sport-Club
|2-1
|-
| 12 May 1957
| '''First Vienna FC '''
|FC Stadlau
|3-0
|}
===Round 21===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 18 May 1957
|1. Simmeringer SC
|Wiener AC
|2-2
|-
| 18 May 1957
|'''SK Rapid Wien '''
|Kremser SC
|12-1
|-
| 18 May 1957
|'''SC Wacker Wien '''
|Kapfenberger SV
|14-0
|-
| 19 May 1957
|SK Admira Wien
| '''Grazer AK '''
|1-2
|-
| 19 May 1957
| FC Stadlau
| '''FK Austria Wien '''
|0-5
|-
| 19 May 1957
|'''SK Sturm Graz '''
|First Vienna FC
|3-2
|-
| 19 May 1957
| '''Wiener Sport-Club '''
|SV Austria Salzburg
|6-2
|}
===Round 22===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 29 May 1957
|Wiener AC
|SK Admira Wien
|1-1
|-
| 30 May 1957
|SV Austria Salzburg
|SC Wacker Wien
|3-3
|-
| 30 May 1957
|'''FK Austria Wien '''
|SK Sturm Graz
|3-2
|-
| 30 May 1957
|Grazer AK
| '''SK Rapid Wien '''
|1-5
|-
| 30 May 1957
|Kapfenberger SV
| 1. Simmeringer SC
|2-2
|-
| 30 May 1957
|''' Kremser SC '''
|FC Stadlau
|5-1
|-
| 30 May 1957
| Wiener Sport-Club
|''' First Vienna FC '''
|0-1
|}
===Round 23===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 1 June 1957
|1. Simmeringer SC
|'''SV Austria Salzburg'''
|2-4
|-
| 1 June 1957
|SC Wacker Wien
| First Vienna FC
|1-1
|-
| 1 June 1957
|Wiener Sport-Club
|'''FK Austria Wien '''
|0-1
|-
| 2 June 1957
| FC Stadlau
| '''SK Rapid Wien '''
|1-9
|-
| 2 June 1957
|SK Admira Wien
| Kapfenberger SV
|2-2
|-
| 2 June 1957
|''' SK Sturm Graz '''
|Kremser SC
|3-2
|-
| 2 June 1957
| '''Wiener AC'''
|Grazer AK
|5-0
|}
===Round 24===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 8 June 1957
|'''SV Austria Salzburg'''
|SK Admira Wien
|2-1
|-
| 8 June 1957
|'''FK Austria Wien '''
|SC Wacker Wien
|2-1
|-
| 8 June 1957
|'''Grazer AK'''
| FC Stadlau
|9-1
|-
|8 June 1957
| '''SK Rapid Wien '''
| SK Sturm Graz
|5-1
|-
| 9 June 1957
| ''' Kapfenberger SV '''
| Wiener AC
|3-1
|-
| 9 June 1957
|''' Kremser SC '''
|Wiener Sport-Club
|2-1
|-
| 9 June 1957
| '''First Vienna FC '''
|1. Simmeringer SC
|4-1
|}
===Round 25===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 15 June 1957
|1. Simmeringer SC
|FK Austria Wien
|2-2
|-
| 15 June 1957
|Wiener Sport-Club
|'''SK Rapid Wien '''
|0-2
|-
| 16 June 1957
| '''SK Admira Wien '''
|First Vienna FC
|2-1
|-
|16 June 1957
| ''' Kapfenberger SV '''
|Grazer AK
|4-0
|-
| 16 June 1957
| '''SK Sturm Graz '''
| FC Stadlau
|5-1
|-
| 16 June 1957
|''' SC Wacker Wien '''
|Kremser SC
|5-2
|-
| 16 June 1957
| '''Wiener AC'''
|SV Austria Salzburg
|8-0
|}
===Round 26===
{| class="wikitable"
|-
!Date
! Team 1
!Team2
!
|-
| 19 June 1957
|FK Austria Wien
|'''SK Admira Wien '''
|0-1
|-
| 19 June 1957
|SK Rapid Wien
|''' SC Wacker Wien '''
|2-4
|-
| 20 June 1957
| SV Austria Salzburg
|''' Kapfenberger SV '''
|2-3
|-
| 20 June 1957
| FC Stadlau
|'''Wiener Sport-Club'''
|1-4
|-
| 20 June 1957
| '''Grazer AK '''
| SK Sturm Graz
|3-2
|-
| 16 June 1957
|Kremser SC
|1. Simmeringer SC
|4-4
|-
| 16 June 1957
| '''First Vienna FC '''
|Wiener AC
|3-1
|}
==Champion squad ==
{| class="wikitable" style="text-align:left; background:#E8E8E8; width:90%;"
|-
| style="text-align:center; background:#00794c" | <span style="color:#FFFFFF;">'''SK Rapid Wien'''</span>
|-
|
[[Walter Zeman]], [[Herbert Gartner]], [[Josef Höltl]], [[Ernst Happel]], [[Wilhelm Zaglitsch]], [[Lambert Lenzinger]], [[Gerhard Hanappi]], [[Lothar Bilek]], [[Franz Golobic]], [[Karl Giesser]], [[Robert Körner]] (C), [[Paul Halla]], [[Johann Riegler]], [[Robert Dienst]], [[Alfred Körner]], [[Josef Bertalan]], [[Bruno Mehsarosch]], [[Herbert Schaffranek]] – '''Coach:''' [[Robert Körner]]
|}
<ref name="ref1"/>
==Top scorer==
{| class="wikitable"
|-
! Name
! Club
! Goals
|-
|[[Robert Dienst]]
|SK Rapid Wien
|32
|-
| [[Johann Riegler]]
|SK Rapid Wien
|25
|-
| [[Walter Haummer]]
|SC Wacker Wien
|20
|-
| [[Paul Kozlicek]]
|SC Wacker Wien
|20
|}
<ref name="ref1"/>
{{Austrian Bundesliga seasons}}
==References ==
{{DEFAULTSORT:1956-57 Austrian football championship}}
[[Category:Football in Austria]]
[[Category:Football seasons]]
[[Category:1956 in Europe]]
[[Category:1956 in association football]]
[[Category:1957 in association football]]
{{reflist}}
94jil5lerti5yr2zgy114c6nnwi3up6
User:PieAlt/test.js
2
174537
737314
736523
2026-04-09T08:59:57Z
PieAlt
73198
Test
737314
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui'],function(){
var localApi=new mw.Api();
var metaApi=new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');
function addButton(){
mw.util.addPortletLink('p-tb','#','Find redirects','t-findredirects','List broken redirects');
$('#t-findredirects').on('click',function(e){
e.preventDefault();
showBrokenRedirects();
});
}
function getAdminCount(callback){
localApi.get({
action:'query',
list:'allusers',
augroup:'sysop',
aulimit:'max'
}).done(function(data){
var count=data.query.allusers.length;
callback(count);
});
}
function showBrokenRedirects(){
localApi.get({
action:'query',
list:'querypage',
qppage:'BrokenRedirects',
qplimit:'max'
}).done(function(data){
var redirects=data.query.querypage.results;
var overlay=$('<div>').css({
position:'fixed',top:0,left:0,width:'100%',height:'100%',
background:'rgba(0,0,0,0.6)','z-index':9998
});
var menu=$('<div>').css({
position:'fixed',top:'50%',left:'50%',
transform:'translate(-50%,-50%)',
background:'#fff',
padding:'0',
'z-index':9999,
width:'720px',
'max-height':'85%',
display:'flex',
'flex-direction':'column',
'border-radius':'12px',
overflow:'hidden',
'box-shadow':'0 10px 28px rgba(0,0,0,0.45)'
});
var header=$('<div>').css({
padding:'12px 16px',
background:'#2a4b8d',
color:'#fff',
display:'flex',
'justify-content':'space-between',
'align-items':'center'
});
var headerLeft=$('<div>').css({
display:'flex',
'flex-direction':'column'
});
var titleText=$('<span>').text('Broken Redirects Manager').css({
'font-weight':'bold',
'font-size':'1.05em'
});
var adminCountText=$('<span>').text('Loading admins...').css({
'font-size':'0.8em',
opacity:'0.85'
});
headerLeft.append(titleText,adminCountText);
getAdminCount(function(count){
adminCountText.text('Active administrators: '+count);
});
var closeBtn=$('<button>').text('✖').css({
border:'none',background:'transparent',color:'#fff','font-size':'1.2em',cursor:'pointer'
}).on('click',function(){overlay.remove();menu.remove();});
header.append(headerLeft,closeBtn);
var searchBar=$('<input>').attr('placeholder','Search redirects...').css({
width:'calc(100% - 20px)',
margin:'10px',
padding:'6px',
border:'1px solid #ccc',
'border-radius':'6px'
});
var listContainer=$('<div>').css({
overflow:'auto',
flex:'1',
padding:'10px'
});
redirects.forEach(function(item){
var page=item.title;
var ns=item.ns;
if(ns===2||ns===3||ns===118){return;}
localApi.get({action:'query',titles:page,prop:'info'}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){return;}
var entry=$('<div>').css({
display:'flex',
'justify-content':'space-between',
'align-items':'center',
padding:'8px',
margin:'4px 0',
background:'#f8f9fa',
'border-radius':'6px',
'transition':'0.2s'
}).hover(function(){
$(this).css('background','#e9ecef');
},function(){
$(this).css('background','#f8f9fa');
});
var title=$('<span>').text(page).css({
'flex':'1',
'font-size':'0.95em'
});
var btnGroup=$('<div>').css({
display:'flex',
gap:'4px'
});
function makeBtn(label,color,handler){
return $('<button>').text(label).css({
padding:'4px 8px',
border:'none',
'border-radius':'4px',
background:color,
color:'#fff',
cursor:'pointer',
'font-size':'0.8em'
}).on('click',handler);
}
btnGroup.append(
makeBtn('GSR','#6c757d',function(){requestDeletion(page);}),
makeBtn('GSR+Tag','#495057',function(){requestDeletion(page);addDeleteTag(page,true);}),
makeBtn('Tag','#0d6efd',function(){addDeleteTag(page,false);}),
makeBtn('Delete','#dc3545',function(){deletePage(page);}),
makeBtn('Change','#198754',function(){changeRedirectTarget(page);})
);
entry.append(title,btnGroup);
listContainer.append(entry);
});
});
searchBar.on('input',function(){
var val=$(this).val().toLowerCase();
listContainer.children().each(function(){
var txt=$(this).text().toLowerCase();
$(this).toggle(txt.indexOf(val)!==-1);
});
});
menu.append(header,searchBar,listContainer);
$('body').append(overlay).append(menu);
});
}
function requestDeletion(page){
var lang=mw.config.get('wgContentLanguage');
var dbname=mw.config.get('wgDBname');
var prefix='';
if(dbname.includes('wikibooks'))prefix='b:'+lang+':';
else if(dbname.includes('wikiquote'))prefix='q:'+lang+':';
else if(dbname.includes('wikiversity'))prefix='wikiversity:'+lang+':';
else if(dbname.includes('wikisource'))prefix='wikisource:'+lang+':';
else if(dbname.includes('wikivoyage'))prefix='voy:'+lang+':';
else if(dbname.includes('wikispecies'))prefix='wikispecies:';
else if(dbname.includes('wiktionary'))prefix='wikt:'+lang+':';
else if(dbname.includes('wikinews'))prefix='n:'+lang+':';
else if(dbname.includes('commonswiki'))prefix='commons:';
else if(dbname.includes('metawiki'))prefix='m:';
else if(dbname.includes('mediawiki'))prefix='mw:';
else prefix=lang+':';
var formatted='* [[:'+prefix+page+']]: Broken redirect. <small>[[:m:User:PieWriter/BR.js|BR]]</small> ~~~~';
metaApi.postWithToken('csrf',{
action:'edit',
title:'Global sysops/Requests',
summary:'Requesting speedy deletion of broken redirect [[User:PieWriter/BR.js|using tool]]',
appendtext:'\n'+formatted
}).done(function(){alert('Deletion request added for '+page+' on Meta-Wiki');});
}
function addDeleteTag(page,gsr){
var tagText=gsr?'{{delete|Broken redirect|delete gsr}}':'{{delete|Broken redirect}}';
localApi.postWithToken('csrf',{
action:'edit',
title:page,
summary:'Tagging broken redirect ([[:m:User:PieWriter/BR.js|BR]])',
prependtext:tagText+'\n'
}).done(function(){alert('Added deletion tag to '+page+(gsr?' (with gsr)':''));});
}
function deletePage(page){
localApi.postWithToken('csrf',{
action:'delete',
title:page,
reason:'Broken redirect ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){alert('Page '+page+' deleted');})
.fail(function(){alert('Failed to delete '+page+'. You may need admin rights.');});
}
function changeRedirectTarget(page){
var newTarget=prompt('Enter new redirect target for '+page+':');
if(!newTarget)return;
localApi.get({
action:'query',
titles:newTarget
}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){
if(!confirm('Target page does not exist. Continue anyway?'))return;
}
localApi.get({
action:'query',
prop:'revisions',
titles:page,
rvprop:'content',
rvslots:'main',
formatversion:2
}).done(function(data){
var content=data.query.pages[0].revisions[0].slots.main.content;
var newContent;
if(/^#redirect\s*\[\[.*?\]\]/i.test(content)){
newContent=content.replace(/^#redirect\s*\[\[.*?\]\]/i,'#REDIRECT [['+newTarget+']]');
}else{
newContent='#REDIRECT [['+newTarget+']]\n'+content;
}
localApi.postWithToken('csrf',{
action:'edit',
title:page,
text:newContent,
summary:'Changing redirect target ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){
alert('Redirect target for '+page+' changed to '+newTarget);
}).fail(function(){
alert('Failed to change redirect target for '+page);
});
});
});
}
addButton();
});
// </nowiki>
pc5pn92ol6dzm7jsz6dl2mz1lyaniuv
User:PieWriter/common.js
2
174545
737300
735522
2026-04-09T06:12:35Z
PieWriter
72123
Installing [[User:PieWriter/test.js]] ([[mw:User:Iniquity/scriptManager.js|Script Manager]])
737300
javascript
text/javascript
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:PieAlt/test.js&action=raw&ctype=text/javascript'); // Backlink: [[User:PieAlt/test.js]]
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:PieWriter/test.js&action=raw&ctype=text/javascript'); // Backlink: [[User:PieWriter/test.js]]
js935q3kfltlcnvqyljddnwr3ua0lo4
737303
737300
2026-04-09T06:20:02Z
PieWriter
72123
Disabling [[User:PieAlt/test.js]] ([[mw:User:Iniquity/scriptManager.js|Script Manager]])
737303
javascript
text/javascript
//mw.loader.load('//test.wikipedia.org/w/index.php?title=User:PieAlt/test.js&action=raw&ctype=text/javascript'); // Backlink: [[User:PieAlt/test.js]]
mw.loader.load('//test.wikipedia.org/w/index.php?title=User:PieWriter/test.js&action=raw&ctype=text/javascript'); // Backlink: [[User:PieWriter/test.js]]
01lrymm7qmc2n758704pj6k6osdy2ar
737305
737303
2026-04-09T06:44:38Z
PieWriter
72123
Uninstalling [[User:PieWriter/test.js]] ([[mw:User:Iniquity/scriptManager.js|Script Manager]])
737305
javascript
text/javascript
//mw.loader.load('//test.wikipedia.org/w/index.php?title=User:PieAlt/test.js&action=raw&ctype=text/javascript'); // Backlink: [[User:PieAlt/test.js]]
49dkikadxr0ol7h49b1r4g6vcym8te7
Wikipedia:Requests/Permissions/PieAlt
4
174623
737173
736831
2026-04-08T13:08:33Z
Barras
6527
/* PieAlt */ cmt
737173
wikitext
text/x-wiki
<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.-->
=== [[User:PieAlt|PieAlt]] ===
* {{User3|PieAlt}}, [[Special:CentralAuth/PieAlt|global contribs]] 04:43, 3 April 2026 (UTC)
* '''Motive for request:''' <!-- Important! Please include a good motive for your request here. --> Please grant admin rights to my alt account. I am using it to test my script and I need admin rights to have the right to delete pages. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 04:43, 3 April 2026 (UTC)
* '''Requested rights:''' <!-- Please include the rights requested (example: Administrator, Bureaucrat, etc.) --> Administrator
* '''Comments:''' <!-- Comments of other users --> {{ping|Barras}}
Hello PieWriter. You need to share what kind of scripts you'd like to test and for how long you need the permission. Since you don't have admin rights on a production wiki, you will usually only be granted the rights for a limited period. Furthermore, I'd like to know why you want the flag on your secondary and not on your main account. -[[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:31, 3 April 2026 (UTC)
:@[[User:Barras|Barras]] Some scripts I want to test is [[User:PieAlt/test.js]], in which I am planning to add a delete function. Have admin rights allows me to test if the function works properly before using on wikiversity. I do have curator right, which let me delete pages (semi-admin). I want it on my secondary account as I use it to test scripts on mainly on that account and I want to keep testing activity clearly separated from my regular editing. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:25, 4 April 2026 (UTC)
:@[[User:Barras|Barras]] Any update? [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:43, 6 April 2026 (UTC)
::I see no reason why the admin flag should be added to an alternate account instead of your regular account. {{ping|Vermont|Xaosflux|Ameisenigel}} would either of you give a second opinion? -[[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>]] 13:08, 8 April 2026 (UTC)
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
ausmpd2p619gwlezwspgtrz74vi7bvv
737175
737173
2026-04-08T13:15:48Z
Xaosflux
101
/* PieAlt */ Reply
737175
wikitext
text/x-wiki
<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.-->
=== [[User:PieAlt|PieAlt]] ===
* {{User3|PieAlt}}, [[Special:CentralAuth/PieAlt|global contribs]] 04:43, 3 April 2026 (UTC)
* '''Motive for request:''' <!-- Important! Please include a good motive for your request here. --> Please grant admin rights to my alt account. I am using it to test my script and I need admin rights to have the right to delete pages. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 04:43, 3 April 2026 (UTC)
* '''Requested rights:''' <!-- Please include the rights requested (example: Administrator, Bureaucrat, etc.) --> Administrator
* '''Comments:''' <!-- Comments of other users --> {{ping|Barras}}
Hello PieWriter. You need to share what kind of scripts you'd like to test and for how long you need the permission. Since you don't have admin rights on a production wiki, you will usually only be granted the rights for a limited period. Furthermore, I'd like to know why you want the flag on your secondary and not on your main account. -[[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:31, 3 April 2026 (UTC)
:@[[User:Barras|Barras]] Some scripts I want to test is [[User:PieAlt/test.js]], in which I am planning to add a delete function. Have admin rights allows me to test if the function works properly before using on wikiversity. I do have curator right, which let me delete pages (semi-admin). I want it on my secondary account as I use it to test scripts on mainly on that account and I want to keep testing activity clearly separated from my regular editing. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:25, 4 April 2026 (UTC)
:@[[User:Barras|Barras]] Any update? [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:43, 6 April 2026 (UTC)
::I see no reason why the admin flag should be added to an alternate account instead of your regular account. {{ping|Vermont|Xaosflux|Ameisenigel}} would either of you give a second opinion? -[[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>]] 13:08, 8 April 2026 (UTC)
:::Someone that has been a sub-admin on medium project for like a week - it's not a "hard decline" - but this is something they should be able to test locally in their own userspace as well. A short term (60-day?) temp grant is OK I guess (scope limited to deleting pages created by the user). -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:15, 8 April 2026 (UTC)
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
57n5tkpep4nzfwt2ao3x04h97y8744c
737205
737175
2026-04-08T16:46:41Z
Ameisenigel
43722
/* PieAlt */ Reply
737205
wikitext
text/x-wiki
<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.-->
=== [[User:PieAlt|PieAlt]] ===
* {{User3|PieAlt}}, [[Special:CentralAuth/PieAlt|global contribs]] 04:43, 3 April 2026 (UTC)
* '''Motive for request:''' <!-- Important! Please include a good motive for your request here. --> Please grant admin rights to my alt account. I am using it to test my script and I need admin rights to have the right to delete pages. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 04:43, 3 April 2026 (UTC)
* '''Requested rights:''' <!-- Please include the rights requested (example: Administrator, Bureaucrat, etc.) --> Administrator
* '''Comments:''' <!-- Comments of other users --> {{ping|Barras}}
Hello PieWriter. You need to share what kind of scripts you'd like to test and for how long you need the permission. Since you don't have admin rights on a production wiki, you will usually only be granted the rights for a limited period. Furthermore, I'd like to know why you want the flag on your secondary and not on your main account. -[[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:31, 3 April 2026 (UTC)
:@[[User:Barras|Barras]] Some scripts I want to test is [[User:PieAlt/test.js]], in which I am planning to add a delete function. Have admin rights allows me to test if the function works properly before using on wikiversity. I do have curator right, which let me delete pages (semi-admin). I want it on my secondary account as I use it to test scripts on mainly on that account and I want to keep testing activity clearly separated from my regular editing. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:25, 4 April 2026 (UTC)
:@[[User:Barras|Barras]] Any update? [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:43, 6 April 2026 (UTC)
::I see no reason why the admin flag should be added to an alternate account instead of your regular account. {{ping|Vermont|Xaosflux|Ameisenigel}} would either of you give a second opinion? -[[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>]] 13:08, 8 April 2026 (UTC)
:::Someone that has been a sub-admin on medium project for like a week - it's not a "hard decline" - but this is something they should be able to test locally in their own userspace as well. A short term (60-day?) temp grant is OK I guess (scope limited to deleting pages created by the user). -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:15, 8 April 2026 (UTC)
:::I think we can grant the rights for a short period, since the user has access to the deletion tools in a production wiki. I would also prefer to grant this to the main account and not to an alternative account. --[[User:Ameisenigel|Ameisenigel]] ([[User talk:Ameisenigel|talk]]) 16:46, 8 April 2026 (UTC)
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
5zotie2cbjft1apaoodp00zpr1zk86d
737219
737205
2026-04-08T18:10:52Z
Xaosflux
101
/* PieAlt */ Reply
737219
wikitext
text/x-wiki
<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.-->
=== [[User:PieAlt|PieAlt]] ===
* {{User3|PieAlt}}, [[Special:CentralAuth/PieAlt|global contribs]] 04:43, 3 April 2026 (UTC)
* '''Motive for request:''' <!-- Important! Please include a good motive for your request here. --> Please grant admin rights to my alt account. I am using it to test my script and I need admin rights to have the right to delete pages. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 04:43, 3 April 2026 (UTC)
* '''Requested rights:''' <!-- Please include the rights requested (example: Administrator, Bureaucrat, etc.) --> Administrator
* '''Comments:''' <!-- Comments of other users --> {{ping|Barras}}
Hello PieWriter. You need to share what kind of scripts you'd like to test and for how long you need the permission. Since you don't have admin rights on a production wiki, you will usually only be granted the rights for a limited period. Furthermore, I'd like to know why you want the flag on your secondary and not on your main account. -[[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:31, 3 April 2026 (UTC)
:@[[User:Barras|Barras]] Some scripts I want to test is [[User:PieAlt/test.js]], in which I am planning to add a delete function. Have admin rights allows me to test if the function works properly before using on wikiversity. I do have curator right, which let me delete pages (semi-admin). I want it on my secondary account as I use it to test scripts on mainly on that account and I want to keep testing activity clearly separated from my regular editing. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:25, 4 April 2026 (UTC)
:@[[User:Barras|Barras]] Any update? [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:43, 6 April 2026 (UTC)
::I see no reason why the admin flag should be added to an alternate account instead of your regular account. {{ping|Vermont|Xaosflux|Ameisenigel}} would either of you give a second opinion? -[[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>]] 13:08, 8 April 2026 (UTC)
:::Someone that has been a sub-admin on medium project for like a week - it's not a "hard decline" - but this is something they should be able to test locally in their own userspace as well. A short term (60-day?) temp grant is OK I guess (scope limited to deleting pages created by the user). -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:15, 8 April 2026 (UTC)
:::I think we can grant the rights for a short period, since the user has access to the deletion tools in a production wiki. I would also prefer to grant this to the main account and not to an alternative account. --[[User:Ameisenigel|Ameisenigel]] ([[User talk:Ameisenigel|talk]]) 16:46, 8 April 2026 (UTC)
::::@[[User:PieWriter|PieWriter]] can you use your normal account for this? -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 18:10, 8 April 2026 (UTC)
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
03wqkk2oeib0rz1unpro9nzahzs9fq0
737298
737219
2026-04-09T06:09:56Z
PieWriter
72123
/* PieAlt */ Reply
737298
wikitext
text/x-wiki
<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.-->
=== [[User:PieAlt|PieAlt]] ===
* {{User3|PieAlt}}, [[Special:CentralAuth/PieAlt|global contribs]] 04:43, 3 April 2026 (UTC)
* '''Motive for request:''' <!-- Important! Please include a good motive for your request here. --> Please grant admin rights to my alt account. I am using it to test my script and I need admin rights to have the right to delete pages. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 04:43, 3 April 2026 (UTC)
* '''Requested rights:''' <!-- Please include the rights requested (example: Administrator, Bureaucrat, etc.) --> Administrator
* '''Comments:''' <!-- Comments of other users --> {{ping|Barras}}
Hello PieWriter. You need to share what kind of scripts you'd like to test and for how long you need the permission. Since you don't have admin rights on a production wiki, you will usually only be granted the rights for a limited period. Furthermore, I'd like to know why you want the flag on your secondary and not on your main account. -[[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:31, 3 April 2026 (UTC)
:@[[User:Barras|Barras]] Some scripts I want to test is [[User:PieAlt/test.js]], in which I am planning to add a delete function. Have admin rights allows me to test if the function works properly before using on wikiversity. I do have curator right, which let me delete pages (semi-admin). I want it on my secondary account as I use it to test scripts on mainly on that account and I want to keep testing activity clearly separated from my regular editing. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:25, 4 April 2026 (UTC)
:@[[User:Barras|Barras]] Any update? [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:43, 6 April 2026 (UTC)
::I see no reason why the admin flag should be added to an alternate account instead of your regular account. {{ping|Vermont|Xaosflux|Ameisenigel}} would either of you give a second opinion? -[[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>]] 13:08, 8 April 2026 (UTC)
:::Someone that has been a sub-admin on medium project for like a week - it's not a "hard decline" - but this is something they should be able to test locally in their own userspace as well. A short term (60-day?) temp grant is OK I guess (scope limited to deleting pages created by the user). -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:15, 8 April 2026 (UTC)
:::I think we can grant the rights for a short period, since the user has access to the deletion tools in a production wiki. I would also prefer to grant this to the main account and not to an alternative account. --[[User:Ameisenigel|Ameisenigel]] ([[User talk:Ameisenigel|talk]]) 16:46, 8 April 2026 (UTC)
::::@[[User:PieWriter|PieWriter]] can you use your normal account for this? -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 18:10, 8 April 2026 (UTC)
:::::@[[User:Xaosflux|Xaosflux]] Weird, but I did not get the ping. But sure I can use this account and I don’t mind a temp permission. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 06:09, 9 April 2026 (UTC)
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
jyn89v1my751546ryhae2takue17h28
737327
737298
2026-04-09T11:37:38Z
Barras
6527
d
737327
wikitext
text/x-wiki
{{Request-done|1=[[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>]] 11:37, 9 April 2026 (UTC)|2=As per discussion below, 2 months temporary permission granted on main account instead of the secondary account.}}
<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.-->
=== [[User:PieAlt|PieAlt]] ===
* {{User3|PieAlt}}, [[Special:CentralAuth/PieAlt|global contribs]] 04:43, 3 April 2026 (UTC)
* '''Motive for request:''' <!-- Important! Please include a good motive for your request here. --> Please grant admin rights to my alt account. I am using it to test my script and I need admin rights to have the right to delete pages. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 04:43, 3 April 2026 (UTC)
* '''Requested rights:''' <!-- Please include the rights requested (example: Administrator, Bureaucrat, etc.) --> Administrator
* '''Comments:''' <!-- Comments of other users --> {{ping|Barras}}
Hello PieWriter. You need to share what kind of scripts you'd like to test and for how long you need the permission. Since you don't have admin rights on a production wiki, you will usually only be granted the rights for a limited period. Furthermore, I'd like to know why you want the flag on your secondary and not on your main account. -[[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:31, 3 April 2026 (UTC)
:@[[User:Barras|Barras]] Some scripts I want to test is [[User:PieAlt/test.js]], in which I am planning to add a delete function. Have admin rights allows me to test if the function works properly before using on wikiversity. I do have curator right, which let me delete pages (semi-admin). I want it on my secondary account as I use it to test scripts on mainly on that account and I want to keep testing activity clearly separated from my regular editing. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:25, 4 April 2026 (UTC)
:@[[User:Barras|Barras]] Any update? [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 00:43, 6 April 2026 (UTC)
::I see no reason why the admin flag should be added to an alternate account instead of your regular account. {{ping|Vermont|Xaosflux|Ameisenigel}} would either of you give a second opinion? -[[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>]] 13:08, 8 April 2026 (UTC)
:::Someone that has been a sub-admin on medium project for like a week - it's not a "hard decline" - but this is something they should be able to test locally in their own userspace as well. A short term (60-day?) temp grant is OK I guess (scope limited to deleting pages created by the user). -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:15, 8 April 2026 (UTC)
:::I think we can grant the rights for a short period, since the user has access to the deletion tools in a production wiki. I would also prefer to grant this to the main account and not to an alternative account. --[[User:Ameisenigel|Ameisenigel]] ([[User talk:Ameisenigel|talk]]) 16:46, 8 April 2026 (UTC)
::::@[[User:PieWriter|PieWriter]] can you use your normal account for this? -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 18:10, 8 April 2026 (UTC)
:::::@[[User:Xaosflux|Xaosflux]] Weird, but I did not get the ping. But sure I can use this account and I don’t mind a temp permission. [[User:PieWriter|PieWriter]] ([[User talk:PieWriter|talk]]) 06:09, 9 April 2026 (UTC)
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
{{Request closed}}
19rceg08t8i34gadurf2zubbx3pyl71
User talk:JWBTH/CD example page
3
174624
737237
736092
2026-04-08T18:32:50Z
JWBTH
52211
/* Proposal to update the main infobox image */
737237
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:15, 2 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:BluePenguin|BluePenguin]] ([[User talk:BluePenguin|talk]]) 15:42, 2 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text, specifically the second paragraph under the "Early years" heading. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:30, 2 April 2026 (UTC)
:: That's a great compromise, HistorianX. I completely agree with moving the current image down to the history section instead of removing it altogether. I'll draft the proposed layout in my [[User:WikiEditor99/sandbox|sandbox]] first so everyone can see how it flows before we take it live. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 20:12, 2 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
33d7k7412motiw2mzku317xq67ma71b
737238
737237
2026-04-08T18:37:23Z
JWBTH
52211
/* Proposal to update the main infobox image */
737238
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:15, 2 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text, specifically the second paragraph under the "Early years" heading. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:30, 2 April 2026 (UTC)
:: That's a great compromise, HistorianX. I completely agree with moving the current image down to the history section instead of removing it altogether. I'll draft the proposed layout in my [[User:WikiEditor99/sandbox|sandbox]] first so everyone can see how it flows before we take it live. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 20:12, 2 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
aai9lz2hzipct3hmy3wnp08qbm1ci5f
737239
737238
2026-04-08T18:37:48Z
JWBTH
52211
/* Proposal to update the main infobox image */
737239
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:15, 2 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text, specifically the second paragraph under the "Early years" heading. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:30, 2 April 2026 (UTC)
:: That's a great compromise, HistorianX. I completely agree with moving the current image down to the history section instead of removing it altogether. I'll draft the proposed layout in my [[User:WikiEditor99/sandbox|sandbox]] first so everyone can see how it flows before we take it live. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 20:12, 2 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:BluePenguin|BluePenguin]] ([[User talk:BluePenguin|talk]]) 15:42, 2 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
35jj7j5f5rrrwsc0279nub1tn1ow28c
737241
737239
2026-04-08T18:43:39Z
JWBTH
52211
/* Proposal to update the main infobox image */
737241
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:15, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text, specifically the second paragraph under the "Early years" heading. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:30, 8 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:BluePenguin|BluePenguin]] ([[User talk:BluePenguin|talk]]) 15:42, 8 April 2026 (UTC)
: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
h5geqdbkoi5m8ycx1b1eetm0ud0bryc
737242
737241
2026-04-08T18:45:11Z
JWBTH
52211
737242
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:25, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text, specifically the second paragraph under the "Early years" heading. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:30, 8 April 2026 (UTC)
: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
nh2qvw56mup5k6nvlsz68t6k6tlqoml
737243
737242
2026-04-08T18:47:37Z
JWBTH
52211
737243
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text, specifically the second paragraph under the "Early years" heading. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:30, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:BluePenguin|BluePenguin]] ([[User talk:BluePenguin|talk]]) 14:47, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
d30i6w4he3ozfmkqtlsy2npqdbe4otf
737244
737243
2026-04-08T18:49:06Z
JWBTH
52211
737244
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:45, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:BluePenguin|BluePenguin]] ([[User talk:BluePenguin|talk]]) 18:47, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
flol1u6gfv0xk9lm0bg80ijhg20bvtg
737246
737244
2026-04-08T18:55:33Z
JWBTH
52211
737246
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? If there are no major objections in the next few days, I'll go ahead and boldly make the change to the live article. --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:45, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
2aqcr1ek3m10d12b53wy17h2i2zw5ab
737250
737246
2026-04-08T19:04:54Z
JWBTH
52211
737250
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:Feline Fanatic|FelineFanatic]] ([[User talk:Feline Fanatic|talk]]) 16:40, 8 April 2026 (UTC)
::: Collapsed reply. --[[User:Talk page lurker|Talk page lurker]] ([[User talk:Talk page lurker|talk]]) 17:40, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 18:45, 8 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:Blue Penguin|BluePenguin]] ([[User talk:Blue Penguin|talk]]) 18:47, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
d3rsjb9xjj31lau6rnzpien9yp1esvm
737251
737250
2026-04-08T19:06:29Z
JWBTH
52211
737251
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|210px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:Feline Fanatic|FelineFanatic]] ([[User talk:Feline Fanatic|talk]]) 16:40, 8 April 2026 (UTC)
::: Collapsed reply. --[[User:Talk page lurker|Talk page lurker]] ([[User talk:Talk page lurker|talk]]) 17:40, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 19:03, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
k7xixcusvk7ergk8fdwuonlck3nywlr
737253
737251
2026-04-08T19:09:22Z
JWBTH
52211
737253
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|200px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:Feline Fanatic|FelineFanatic]] ([[User talk:Feline Fanatic|talk]]) 16:40, 8 April 2026 (UTC)
::: Collapsed reply. --[[User:User 4920|User 4920]] ([[User talk:User 4920|talk]]) 17:40, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text. Perhaps we should keep the old one and just move it down into the body of the article. --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 19:08, 8 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:Blue Penguin|BluePenguin]] ([[User talk:Blue Penguin|talk]]) 18:47, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
o70ibvz9j79c0cnfp5p1y5i6rn0z4up
737254
737253
2026-04-08T19:10:05Z
JWBTH
52211
737254
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|200px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:Feline Fanatic|FelineFanatic]] ([[User talk:Feline Fanatic|talk]]) 16:40, 8 April 2026 (UTC)
::: Collapsed reply. --[[User:User 4920|User 4920]] ([[User talk:User 4920|talk]]) 17:40, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text. Perhaps we should keep the old one and just move it down into the body of the article? --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 19:08, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
f5wqi30514k74uarora5ff4rgj4onbi
737255
737254
2026-04-08T19:11:22Z
JWBTH
52211
737255
wikitext
text/x-wiki
== Archive 15 moved to subpage ==
(Bot message text...)
== RfC: Neutral point of view regarding the "Criticism" section ==
(Long-winded debate text...)
== Proposal to update the main infobox image ==
[[File:Macaca nigra self-portrait large.jpg|thumb|200px|right|Proposed replacement]]
Hey everyone, I've noticed that the current image in the infobox is quite outdated and doesn't really reflect the recent consensus we reached regarding the [[Wikipedia:Manual of Style/Images|MOS on images]].
I propose we replace it with this widely known alternative from Wikimedia Commons. This new image has much better lighting, is public domain, and directly illustrates the core subject looking right at the camera.
What are your thoughts? --[[User:WikiEditor99|WikiEditor99]] ([[User talk:WikiEditor99|talk]]) 14:45, 8 April 2026 (UTC)
: Collapsed reply. --[[User:DesertRose87|DesertRose87]] ([[User talk:DesertRose87|talk]]) 15:42, 8 April 2026 (UTC)
:: Collapsed reply. --[[User:Feline Fanatic|FelineFanatic]] ([[User talk:Feline Fanatic|talk]]) 16:40, 8 April 2026 (UTC)
::: Collapsed reply. --[[User:User 4920|User 4920]] ([[User talk:User 4920|talk]]) 17:40, 8 April 2026 (UTC)
: I'm going to have to play devil's advocate here and '''oppose''' replacing it entirely. While the charm of the new photo is undeniable, the historical context of the ''current'' image is highly relevant to the article's text. Perhaps we should keep the old one and just move it down into the body of the article. --[[User:HistorianX|HistorianX]] ([[User talk:HistorianX|talk]]) 19:08, 8 April 2026 (UTC)
: I generally support this change. The new image is definitely a step up in personality and clarity. However, have we checked if the resolution is high enough to look crisp for users on high-density screens? --[[User:Blue Penguin|BluePenguin]] ([[User talk:Blue Penguin|talk]]) 19:10, 8 April 2026 (UTC)
== Missing secondary sources in the 'Legacy' section ==
(Editor request text...)
== Good Article nomination: Final checklist ==
(Criteria list...) [[User:Example|Example]] 17:48, 15 February 2026 (UTC)
== Typo ==
(Short message about a misspelling...)
43dh3lpiieoiugi5bk4n3u50438l0p6
User:MrJaroslavik/GlobalCheckUserStats.js
2
174673
737181
737126
2026-04-08T13:24:16Z
MrJaroslavik
44012
737181
javascript
text/javascript
// GlobalCheckUserStats.js
// Features:
// - Global Audit: Scans CheckUser logs across 900+ Wikimedia projects at once.
// - Smart Categorization: Identifies roles (Local CU, Steward, Staff, etc.).
// - Steward Logic: Detects temporary access, exact durations, and longest active periods.
// - Deep Scan: Automated pagination to bypass the 500-entry API limit (essential for enwiki).
// - Robust Connection: Automated retries for 429 rate-limits and custom domain mapping.
// - Full Reporting: Outputs sortable Wikitables and detailed rights change logs.
// - Custom UI: Integrated control panel on [[Special:BlankPage/GlobalCheckUserStats]].
// - With help of Gemini 3
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').indexOf('GlobalCheckUserStats') === -1) return;
const rawWikis = ['abstractwiki','abwiki','acewiki','adywiki','afwiki','afwikibooks','afwikiquote','afwiktionary','alswiki','altwiki','amiwiki','amwiki','amwiktionary','angwiki','angwiktionary','annwiki','anpwiki','anwiki','anwiktionary','arcwiki','arwiki','arwikibooks','arwikimedia','arwikinews','arwikiquote','arwikisource','arwikiversity','arwiktionary','arywiki','arzwiki','astwiki','astwiktionary','aswiki','aswikiquote','aswikisource','atjwiki','avkwiki','avwiki','awawiki','aywiki','aywiktionary','azbwiki','azwiki','azwikibooks','azwikiquote','azwikisource','azwiktionary','banwiki','banwikisource','barwiki','bat_smgwiki','bawiki','bawikibooks','bbcwiki','bclwiki','bclwikiquote','bclwikisource','bclwiktionary','bdrwiki','bdwikimedia','be_x_oldwiki','betawikiversity','bewiki','bewikibooks','bewikimedia','bewikiquote','bewikisource','bewiktionary','bewwiki','bewwiktionary','bgwiki','bgwikibooks','bgwikiquote','bgwikisource','bgwiktionary','bhwiki','biwiki','bjnwiki','bjnwikiquote','bjnwiktionary','blkwiki','blkwiktionary','bmwiki','bnwiki','bnwikibooks','bnwikiquote','bnwikisource','bnwikivoyage','bnwiktionary','bowiki','bpywiki','brwiki','brwikimedia','brwikiquote','brwikisource','brwiktionary','bswiki','bswikibooks','bswikinews','bswikiquote','bswikisource','bswiktionary','btmwiki','btmwiktionary','bugwiki','bxrwiki','cawiki','cawikibooks','cawikimedia','cawikinews','cawikiquote','cawikisource','cawiktionary','cbk_zamwiki','cdowiki','cebwiki','cewiki','chrwiki','chrwiktionary','chwiki','chywiki','ckbwiki','ckbwiktionary','commonswiki','cowiki','cowikimedia','cowiktionary','crhwiki','csbwiki','csbwiktionary','cswiki','cswikibooks','cswikinews','cswikiquote','cswikisource','cswikiversity','cswikivoyage','cswiktionary','cuwiki','cvwiki','cvwikibooks','cywiki','cywikibooks','cywikiquote','cywikisource','cywiktionary','dagwiki','dawiki','dawikibooks','dawikiquote','dawikisource','dawiktionary','dewiki','dewikibooks','dewikinews','dewikiquote','dewikisource','dewikiversity','dewikivoyage','dewiktionary','dgawiki','dinwiki','diqwiki','diqwiktionary','dkwikimedia','dsbwiki','dtpwiki','dtywiki','dvwiki','dvwiktionary','dzwiki','eewiki','elwiki','elwikibooks','elwikinews','elwikiquote','elwikisource','elwikiversity','elwikivoyage','elwiktionary','emlwiki','enwiki','enwikibooks','enwikinews','enwikiquote','enwikisource','enwikiversity','enwikivoyage','enwiktionary','eowiki','eowikibooks','eowikinews','eowikiquote','eowikisource','eowikivoyage','eowiktionary','eswiki','eswikibooks','eswikinews','eswikiquote','eswikisource','eswikiversity','eswikivoyage','eswiktionary','etwiki','etwikibooks','eewikimedia','etwikiquote','etwikisource','etwiktionary','euwiki','euwikibooks','euwikiquote','euwikisource','euwiktionary','extwiki','fatwiki','fawiki','fawikibooks','fawikinews','fawikiquote','fawikisource','fawikivoyage','fawiktionary','ffwiki','fiu_vrowiki','fiwiki','fiwikibooks','fiwikimedia','fiwikinews','fiwikiquote','fiwikisource','fiwikiversity','fiwikivoyage','fiwiktionary','fjwiki','fjwiktionary','fonwiki','foundationwiki','fowiki','fowikisource','fowiktionary','frpwiki','frrwiki','frwiki','frwikibooks','frwikinews','frwikiquote','frwikisource','frwikiversity','frwikivoyage','frwiktionary','furwiki','fywiki','fywikibooks','fywiktionary','gagwiki','ganwiki','gawiki','gawiktionary','gcrwiki','gdwiki','gdwiktionary','glkwiki','glwiki','glwikibooks','glwikiquote','glwikisource','glwiktionary','gnwiki','gnwiktionary','gomwiki','gomwiktionary','gorwiki','gorwikiquote','gorwiktionary','gotwiki','gpewiki','gucwiki','gurwiki','guwiki','guwikiquote','guwikisource','guwiktionary','guwwiki','guwwikinews','guwwikiquote','guwwiktionary','gvwiki','gvwiktionary','hakwiki','hawiki','hawiktionary','hawwiki','hewiki','hewikibooks','hewikinews','hewikiquote','hewikisource','hewikivoyage','hewiktionary','hifwiki','hifwiktionary','hiwiki','hiwikibooks','hiwikiquote','hiwikisource','hiwikiversity','hiwikivoyage','hiwiktionary','hrwiki','hrwikibooks','hrwikiquote','hrwikisource','hrwiktionary','hsbwiki','hsbwiktionary','htwiki','huwiki','huwikibooks','huwikisource','huwiktionary','hywiki','hywikibooks','hywikiquote','hywikisource','hywiktionary','hywwiki','iawiki','iawikibooks','iawiktionary','ibawiki','idwiki','idwikibooks','idwikiquote','idwikisource','idwikivoyage','idwiktionary','iewiki','iewiktionary','iglwiki','igwiki','igwikiquote','igwiktionary','ikwiki','ilowiki','incubatorwiki','inhwiki','iowiki','iowiktionary','iswiki','iswikibooks','iswikiquote','iswikisource','iswiktionary','itwiki','itwikibooks','itwikinews','itwikiquote','itwikisource','itwikiversity','itwikivoyage','itwiktionary','iuwiki','iuwiktionary','jamwiki','jawiki','jawikibooks','jawikinews','jawikisource','jawikiversity','jawikivoyage','jawiktionary','jbowiki','jbowiktionary','jvwiki','jvwikisource','jvwiktionary','kaawiki','kaawiktionary','kabwiki','kaiwiki','kajwiki','kawiki','kawikibooks','kawikiquote','kawikisource','kawiktionary','kbdwiki','kbdwiktionary','kbpwiki','kcgwiki','kcgwiktionary','kgewiki','kgwiki','kiwiki','kkwiki','kkwikibooks','kkwiktionary','klwiktionary','kmwiki','kmwikibooks','kmwiktionary','kncwiki','knwiki','knwikiquote','knwikisource','knwiktionary','koiwiki','kowiki','kowikibooks','kowikinews','kowikiquote','kowikisource','kowikiversity','kowiktionary','krcwiki','kshwiki','kswiki','kswiktionary','kuswiki','kuwiki','kuwikibooks','kuwikiquote','kuwiktionary','kvwiki','kwwiki','kwwiktionary','kywiki','kywikiquote','kywiktionary','labswiki','ladwiki','lawiki','lawikibooks','lawikiquote','lawikisource','lawiktionary','lbewiki','lbwiki','lbwiktionary','lezwiki','lfnwiki','lgwiki','lijwiki','lijwikisource','liwiki','liwikibooks','liwikinews','liwikiquote','liwikisource','liwiktionary','lldwiki','lmowiki','lmowiktionary','lnwiki','lnwiktionary','lowiki','lowiktionary','ltgwiki','ltwiki','ltwikibooks','ltwikiquote','ltwikisource','ltwiktionary','lvwiki','lvwiktionary','madwiki','madwikisource','madwiktionary','maiwiki','map_bmswiki','mdfwiki','mediawikiwiki','metawiki','mgwiki','mgwikibooks','mgwiktionary','mhrwiki','minwiki','minwikibooks','minwikisource','minwiktionary','miwiki','miwiktionary','mkwiki','mkwikibooks','mkwikimedia','mkwikisource','mkwiktionary','mlwiki','mlwikibooks','mlwikiquote','mlwikisource','mlwiktionary','mniwiki','mniwiktionary','mnwiki','mnwiktionary','mnwwiki','mnwwiktionary','moswiki','mrjwiki','mrwiki','mrwikibooks','mrwikiquote','mrwikisource','mrwiktionary','mswiki','mswikibooks','mswikiquote','mswikisource','mswiktionary','mtwiki','mtwiktionary','mwlwiki','mxwikimedia','myvwiki','mywiki','mywikisource','mywiktionary','mznwiki','nahwiki','nahwiktionary','napwiki','napwikisource','nawiktionary','nds_nlwiki','ndswiki','ndswiktionary','newiki','newikibooks','newiktionary','newwiki','niawiki','niawiktionary','nlwiki','nlwikibooks','nlwikimedia','nlwikinews','nlwikiquote','nlwikisource','nlwikivoyage','nlwiktionary','nnwiki','nnwikiquote','nnwiktionary','novwiki','nowiki','nowikibooks','nowikimedia','nowikinews','nowikiquote','nowikisource','nowiktionary','nqowiki','nrmwiki','nrwiki','nsowiki','nupwiki','nvwiki','nycwikimedia','nywiki','ocwiki','ocwikibooks','ocwiktionary','olowiki','omwiki','omwiktionary','orwiki','orwikisource','orwiktionary','oswiki','outreachwiki','pagwiki','pamwiki','papwiki','pawiki','pawikibooks','pawikisource','pawiktionary','pcdwiki','pcmwiki','pcmwikiquote','pdcwiki','pflwiki','piwiki','plwiki','plwikibooks','plwikimedia','plwikinews','plwikiquote','plwikisource','plwikivoyage','plwiktionary','pmswiki','pmswikisource','pnbwiki','pnbwiktionary','pntwiki','pplwiki','pswiki','pswikivoyage','pswiktionary','ptwiki','ptwikibooks','ptwikimedia','ptwikinews','ptwikiquote','ptwikisource','ptwikiversity','ptwikivoyage','ptwiktionary','pwnwiki','quwiktionary','rkiwiki','rmwiki','rmywiki','rnwiki','roa_rupwiki','roa_rupwiktionary','roa_tarawiki','rowiki','rowikibooks','rowikinews','rowikiquote','rowiktionary','rskwiki','ruewiki','ruwiki','ruwikibooks','ruwikinews','ruwikiquote','ruwikisource','ruwikiversity','ruwikivoyage','ruwiktionary','rwwiki','rwwiktionary','sahwiki','sahwikiquote','sahwikisource','satwiki','satwiktionary','sawiki','sawikibooks','sawikiquote','sawikisource','sawiktionary','scnwiki','scnwiktionary','scowiki','scwiki','sdwiki','sdwiktionary','sewiki','sewikimedia','sgwiki','sgwiktionary','shiwiki','shnwiki','shnwikibooks','shnwikinews','shnwikivoyage','shnwiktionary','shwiki','shwiktionary','shywiktionary','simplewiki','simplewiktionary','siwiki','siwikibooks','siwiktionary','skwiki','skrwiki','skrwiktionary','slwiki','slwikibooks','slwikiquote','slwikisource','slwikiversity','slwiktionary','smnwiki','smwiki','smwiktionary','snwiki','sourceswiki','sowiki','sowiktionary','specieswiki','sqwiki','sqwikibooks','sqwikinews','sqwikiquote','sqwiktionary','srnwiki','srwiki','srwikibooks','srwikinews','srwikiquote','srwikisource','srwiktionary','sswiki','sswiktionary','stqwiki','stwiki','stwiktionary','suwiki','suwikiquote','suwikisource','suwiktionary','svwiki','svwikibooks','svwikinews','svwikiquote','svwikisource','svwikiversity','svwikivoyage','svwiktionary','swwiki','swwiktionary','sylwiki','szlwiki','szywiki','tawiki','tawikibooks','tawikinews','tawikiquote','tawikisource','tawiktionary','taywiki','tcywiki','tcywikisource','tcywiktionary','tddwiki','tewiki','tewikibooks','tewikiquote','tewikisource','tewiktionary','tgwiki','tgwikibooks','tgwiktionary','thwiki','thwikibooks','thwikimedia','thwikiquote','thwikisource','thwiktionary','tigwiki','tiwiki','tiwiktionary','tkwiki','tkwiktionary','tlwiki','tlwikibooks','tlwikiquote','tlwikisource','tlwiktionary','tlywiki','tnwiki','tnwiktionary','tokwiki','towiki','tpiwiki','tpiwiktionary','trvwiki','trwiki','trwikibooks','trwikimedia','trwikinews','trwikiquote','trwikisource','trwikivoyage','trwiktionary','tswiki','tswiktionary','ttwiki','ttwikibooks','ttwikiquote','ttwiktionary','tumwiki','twwiki','twwiktionary','tyvwiki','tywiki','uawikimedia','udmwiki','ugwiki','ugwiktionary','ukwiki','ukwikibooks','ukwikinews','ukwikiquote','ukwikisource','ukwikivoyage','ukwiktionary','urwiki','urwikibooks','urwikiquote','urwikisource','urwiktionary','uzwiki','uzwikiquote','uzwiktionary','vecwiki','vecwikisource','vecwiktionary','vepwiki','vewiki','viwiki','viwikibooks','viwikiquote','viwikisource','viwikivoyage','viwiktionary','vlswiki','vowiki','vowiktionary','warwiki','wawiki','wawikisource','wawiktionary','wikidatawiki','wikimaniawiki','wowiki','wowiktionary','wuuwiki','xalwiki','xhwiki','xmfwiki','yiwiki','yiwikisource','yiwiktionary','yowiki','zawiki','zeawiki','zghwiki','zghwiktionary','zh_classicalwiki','zh_min_nanwiki','zh_min_nanwikisource','zh_min_nanwiktionary','zh_yuewiki','zhwiki','zhwikibooks','zhwikinews','zhwikiquote','zhwikisource','zhwikiversity','zhwikivoyage','zhwiktionary','zuwiki','zuwiktionary','test2wiki','testwiki'];
const DELAY_MS = 800; // Safe balanced speed
const sleep = ms => new Promise(r => setTimeout(r, ms));
let userCache = {};
let historyCache = {}; // Added this to prevent the ReferenceError
function getDomain(db) {
const mapping = {
'commonswiki': 'commons.wikimedia.org', 'metawiki': 'meta.wikimedia.org', 'wikidatawiki': 'www.wikidata.org',
'mediawikiwiki': 'www.mediawiki.org', 'sourceswiki': 'wikisource.org', 'foundationwiki': 'foundation.wikimedia.org',
'incubatorwiki': 'incubator.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org',
'be_x_oldwiki': 'be-tarask.wikipedia.org', 'labswiki': 'wikitech.wikimedia.org',
'wikimaniawiki': 'wikimania.wikimedia.org', 'specieswiki': 'species.wikimedia.org',
'abstractwiki': 'abstract.wikipedia.org', 'betawikiversity': 'beta.wikiversity.org',
'mo_wiki': 'ro.wikipedia.org',
'testwiki': 'test.wikipedia.org', 'test2wiki': 'test2.wikipedia.org',
'wikifunctionswiki': 'www.wikifunctions.org',
'testcommonswiki': 'test-commons.wikimedia.org',
'testwikidatawiki': 'test.wikidata.org',
};
if (mapping[db]) return mapping[db];
const name = db.replace(/_/g, '-');
if (name.endsWith('wikisource')) return name.slice(0, -10) + '.wikisource.org';
if (name.endsWith('wikiversity')) return name.slice(0, -11) + '.wikiversity.org';
if (name.endsWith('wiktionary')) return name.slice(0, -10) + '.wiktionary.org';
if (name.endsWith('wikivoyage')) return name.slice(0, -10) + '.wikivoyage.org';
if (name.endsWith('wikibooks')) return name.slice(0, -9) + '.wikibooks.org';
if (name.endsWith('wikiquote')) return name.slice(0, -9) + '.wikiquote.org';
if (name.endsWith('wikinews')) return name.slice(0, -8) + '.wikinews.org';
if (name.endsWith('wikimedia')) return name.slice(0, -9) + '.wikimedia.org';
if (name.endsWith('wiki')) return name.slice(0, -4) + '.wikipedia.org';
return name + '.wikipedia.org';
}
mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi']).then(function() {
const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { anonymous: true });
const now = new Date();
let results = {}, emptyWikis = [], failedWikis = [], isRunning = false;
function setupUI() {
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
// CHANGE: Dynamically generate dropdown options for years and months
let yearOpts = '';
for (let y = currentYear; y >= 2005; y--) {
yearOpts += `<option value="${y}">${y}</option>`;
}
let monthOptsFrom = '';
let monthOptsTo = '';
for (let m = 1; m <= 12; m++) {
monthOptsFrom += `<option value="${m}" ${m === 1 ? 'selected' : ''}>${m}</option>`;
monthOptsTo += `<option value="${m}" ${m === currentMonth ? 'selected' : ''}>${m}</option>`;
}
$('#firstHeading').text('GlobalCheckUserStats.js');
$('#mw-content-text').empty().append(`
<div style="border:1px solid #a2a9b1; padding:15px; background:#f8f9fa;">
<h3>Audit Range</h3>
From:
<select id="y-f" style="width:70px;">${yearOpts}</select>
<select id="m-f" style="width:50px;">${monthOptsFrom}</select>
To:
<select id="y-t" style="width:70px;">${yearOpts}</select>
<select id="m-t" style="width:50px;">${monthOptsTo}</select>
<button id="start" class="mw-ui-button mw-ui-progressive" style="margin-left:15px;">Run GlobalCheckUserStats.js</button>
<button id="stop" class="mw-ui-button mw-ui-destructive" disabled>Stop</button>
<div id="status-msg" style="margin-top:10px; font-weight:bold; color:#0056b3;">Ready.</div>
<div style="margin-top:5px;"><progress id="bar" value="0" max="${rawWikis.length}" style="width:100%"></progress></div>
<textarea id="out" style="width:100%; height:450px; margin-top:10px; display:none; font-family:monospace; font-size:12px;"></textarea>
</div>
`);
$('#start').click(() => runAudit($('#y-f').val(), $('#m-f').val(), $('#y-t').val(), $('#m-t').val()));
$('#stop').click(() => isRunning = false);
}
// NEW: Fetch monthly averaged editors and total edits from Wikimedia Analytics API
async function fetchWikiMetrics(db, start, end) {
const project = getDomain(db);
const aqsStart = start.split('T')[0].replace(/-/g, '');
const aqsEnd = end.split('T')[0].replace(/-/g, '');
const editorsUrl = `https://wikimedia.org/api/rest_v1/metrics/editors/aggregate/${project}/all-editor-types/all-page-types/all-activity-levels/monthly/${aqsStart}/${aqsEnd}`;
const editsUrl = `https://wikimedia.org/api/rest_v1/metrics/edits/aggregate/${project}/all-editor-types/all-page-types/monthly/${aqsStart}/${aqsEnd}`;
const fetchWithRetry = async (url) => {
let attempts = 0;
while (attempts < 5) {
try {
const res = await fetch(url, { headers: { 'Api-User-Agent': 'GlobalCheckUserStats/1.0 (contact: YourUser)' } });
if (res.status === 429) {
let wait = (attempts + 1) * 5000;
$('#status-msg').text(`AQS Rate limit! Waiting ${wait/1000}s...`);
await sleep(wait);
attempts++;
continue;
}
if (!res.ok) return null;
return await res.json();
} catch (e) {
attempts++;
await sleep(2000);
}
}
return null;
};
try {
const [edRes, etRes] = await Promise.all([
fetchWithRetry(editorsUrl),
fetchWithRetry(editsUrl)
]);
if (!edRes?.items?.[0]?.results || !etRes?.items?.[0]?.results) {
console.warn(`No AQS data for ${db}`);
return { avgEditors: null, totalEdits: null };
}
const editorsResults = edRes.items[0].results;
const avgEditors = editorsResults.reduce((sum, m) => sum + m.editors, 0) / editorsResults.length;
const totalEdits = etRes.items[0].results.reduce((sum, m) => sum + m.edits, 0);
return { avgEditors, totalEdits };
} catch (e) {
return { avgEditors: null, totalEdits: null };
}
}
// Helper function to check global rights history on Meta-Wiki
async function checkGlobalHistory(user, start, end) {
try {
const res = await metaApi.get({
action: 'query',
list: 'logevents',
letype: 'gblrights',
letitle: 'User:' + user,
formatversion: 2
});
const logs = res.query.logevents || [];
const auditStart = new Date(start);
const auditEnd = new Date(end);
let wasSteward = false, wasStaff = false, wasOmbuds = false;
for (const e of logs) {
const ts = new Date(e.timestamp);
const p = e.params || {};
const added = p.newGroups || p[1] || [];
const removed = p.oldGroups || p[0] || [];
// If a removal happened AFTER the audit started, the user held the rights during the period
if (ts > auditStart) {
if (removed.includes('steward')) wasSteward = true;
if (removed.includes('staff')) wasStaff = true;
if (removed.includes('ombuds')) wasOmbuds = true;
}
// If an addition happened BEFORE the audit ended, the user held the rights during the period
if (ts < auditEnd) {
if (added.includes('steward')) wasSteward = true;
if (added.includes('staff')) wasStaff = true;
if (added.includes('ombuds')) wasOmbuds = true;
}
}
return { wasSteward, wasStaff, wasOmbuds };
} catch (e) {
// Fallback for API errors
return { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
async function fetchUserData(user, db, start, end) {
// Priority 0: Use cache for current global groups
if (!userCache[user]) {
try {
const gres = await metaApi.get({ action: 'query', meta: 'globaluserinfo', guiprop: 'groups', guiuser: user, formatversion: 2 });
userCache[user] = gres.query.globaluserinfo.groups || [];
} catch(e) { userCache[user] = []; }
}
const gGroups = userCache[user];
const isGloballyStaff = gGroups.includes('staff');
const isGloballySteward = gGroups.includes('steward');
const isGloballyOmbuds = gGroups.includes('ombuds');
// Check current local CheckUser rights
let isCurrentLocal = false;
try {
const localApi = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
const ures = await localApi.get({ action: 'query', list: 'users', ususers: user, usprop: 'groups', formatversion: 2 });
const lGroups = (ures.query.users[0] && ures.query.users[0].groups) || [];
isCurrentLocal = lGroups.includes('checkuser');
} catch(e) { /* API failure fallback */ }
// Rights log analysis for the specific Wiki
const target = 'User:' + user + '@' + db;
let logText = ''; // Default is empty, filled only if there are logs
let addTime = null, removeTime = null, isSelfAssign = false;
// CHANGE: Variables to track the longest duration and the number of actions
let maxDurationMins = -1;
let longestTimeStr = "";
let assignCount = 0;
try {
const res = await metaApi.get({
action: 'query', list: 'logevents', letype: 'rights', letitle: target,
lestart: end, leend: start, ledir: 'older',
lelimit: 'max', // ADDED: Loads up to 500 logs instead of 10
formatversion: 2
});
const events = res.query.logevents || [];
let filtered = [];
// Variables to pair removal and addition (iterating newest to oldest)
let tempRemoveTime = null;
let tempRemoveTimeStr = "Not removed";
events.forEach(e => {
const p = e.params || {};
const cuAdded = (p.add || []).includes('checkuser') || ((p.newgroups || []).includes('checkuser') && !(p.oldgroups || []).includes('checkuser'));
const cuRemoved = (p.remove || []).includes('checkuser') || (!(p.newgroups || []).includes('checkuser') && (p.oldgroups || []).includes('checkuser'));
const exactTimeLog = e.timestamp.replace('T', ' ').replace('Z', '');
if (cuRemoved) {
tempRemoveTime = new Date(e.timestamp);
tempRemoveTimeStr = exactTimeLog;
// Save the most recent removal for the overall table role categorization
if (!removeTime) {
removeTime = tempRemoveTime;
if (e.user === user || !e.user) isSelfAssign = true;
}
}
if (cuAdded) {
const currentAddTime = new Date(e.timestamp);
let metadataRemoveTime = tempRemoveTime;
let metadataRemoveTimeStr = tempRemoveTimeStr;
// Save the most recent addition for the overall table role categorization
if (!addTime) {
addTime = currentAddTime;
isSelfAssign = (e.user === user);
}
// Fallback: Check expiry in metadata if explicit removal log is missing
const metadata = p.newmetadata || [];
const cuMeta = metadata.find(m => m.group === 'checkuser');
if (cuMeta && cuMeta.expiry && cuMeta.expiry !== 'infinity' && tempRemoveTimeStr === "Not removed") {
metadataRemoveTime = new Date(cuMeta.expiry);
metadataRemoveTimeStr = cuMeta.expiry.replace('T', ' ').replace('Z', '');
if (!removeTime) removeTime = metadataRemoveTime;
}
// Calculate duration for this specific 1-line pair
let pairDuration = "Unknown";
if (metadataRemoveTime) {
const diffMs = Math.abs(metadataRemoveTime - currentAddTime);
const diffMins = Math.round(diffMs / 60000); // CHANGE: Rounds to the nearest whole minute
const days = Math.floor(diffMins / 1440);
const hours = Math.floor((diffMins % 1440) / 60);
const mins = diffMins % 60;
if (days > 0) pairDuration = `${days}d ${hours}h ${mins}m`;
else if (hours > 0) pairDuration = `${hours}h ${mins}m`;
else if (mins > 0) pairDuration = `${mins}m`;
else pairDuration = "<1m";
// CHANGE: Track the total number of assignments and store the absolute longest duration
assignCount++;
if (diffMins > maxDurationMins) {
maxDurationMins = diffMins;
longestTimeStr = pairDuration;
}
}
// Push the combined 1-line format to the log using commas
filtered.push(`* Added: ${exactTimeLog}, Removed: ${metadataRemoveTimeStr}, Duration: ${pairDuration}`);
// Reset pair variables for the next older event in the loop
tempRemoveTime = null;
tempRemoveTimeStr = "Not removed";
}
});
// Edge case: If a removal was found but the addition happened before the audit period
if (tempRemoveTime && tempRemoveTimeStr !== "Not removed") {
filtered.push(`* Added: Before audit period, Removed: ${tempRemoveTimeStr}, Duration: Unknown`);
}
// Joins logs with newlines for the report
if (filtered.length) {
logText = '\n' + filtered.join('\n');
}
} catch (err) { /* Rights log query failed */ }
// --- GLOBAL HISTORY CHECK (WITH CACHE) ---
if (!historyCache[user]) {
if (!isGloballySteward || !isGloballyStaff || !isGloballyOmbuds) {
// Check Meta-Wiki history logs only if user is missing at least one global role today
historyCache[user] = await checkGlobalHistory(user, start, end);
} else {
historyCache[user] = { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
const history = historyCache[user];
// --- HIERARCHY LOGIC (CheckUser Audit Focus) ---
let roleLabel = "";
// 1. STAFF (Highest audit priority for WMF accountability)
if (isGloballyStaff || history.wasStaff) {
roleLabel = isGloballyStaff ? "Current Staff" : "Former Staff (in period)";
}
// 2. LOCAL CHECKUSER (Overrides Steward/Ombudsman roles if local rights exist)
else if (isCurrentLocal) {
roleLabel = "Current Local CheckUser";
}
// 3. STEWARD - SPECIFIC INTERVENTION (Using exact self-assignment duration or max duration if multiple)
else if (longestTimeStr && isSelfAssign) {
if (assignCount > 1) {
roleLabel = `Steward action (Self-assign: longest ${longestTimeStr}, see log)`;
} else {
roleLabel = `Steward action (Self-assign: ${longestTimeStr})`;
}
}
// 4. STEWARD - GENERAL (Global rights usage on external wikis)
else if (isGloballySteward || history.wasSteward) {
roleLabel = isGloballySteward ? "Current Steward" : "Former Steward (in period)";
}
// 5. OMBUDSMAN (Global oversight role)
else if (isGloballyOmbuds || history.wasOmbuds) {
roleLabel = isGloballyOmbuds ? "Current Ombudsman" : "Former Ombudsman (in period)";
}
// 6. FORMER LOCAL CheckUser (Fallback for those who lost rights but performed actions)
else {
roleLabel = "Former Local CheckUser";
}
return { role: roleLabel, log: logText };
}
async function runAudit(yf, mf, yt, mt) {
isRunning = true;
results = {};
emptyWikis = [];
failedWikis = [];
userCache = {};
historyCache = {};
$('#start').prop('disabled', true);
$('#stop').prop('disabled', false);
$('#out').hide();
// Format ISO timestamps for the API
const START = `${yf}-${mf.toString().padStart(2, '0')}-01T00:00:00Z`;
const lastDay = new Date(Date.UTC(yt, mt, 0)).getUTCDate();
const END = `${yt}-${mt.toString().padStart(2, '0')}-${lastDay.toString().padStart(2, '0')}T23:59:59Z`;
// Generate list of months for dynamic table columns
const monthCols = [];
let currY = parseInt(yf), currM = parseInt(mf);
const endY = parseInt(yt), endM = parseInt(mt);
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
while (currY < endY || (currY === endY && currM <= endM)) {
monthCols.push({
key: `${currY}-${String(currM).padStart(2, '0')}`,
label: `${monthNames[currM - 1]} ${currY}` // e.g., "Jan 2024"
});
currM++;
if (currM > 12) { currM = 1; currY++; }
}
for (let i = 0; i < rawWikis.length && isRunning; i++) {
const db = rawWikis[i];
$('#status-msg').text(`Scanning ${db} (${i + 1}/${rawWikis.length})...`);
let successLocal = false;
let continueToken = null; // Token for pagination
while (!successLocal && isRunning) {
try {
const api = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
let params = {
action: 'query',
list: 'checkuserlog',
culfrom: START,
culto: END,
culdir: 'newer',
cullimit: 'max',
formatversion: 2
};
// Add continue token if we are on page 2 or further
if (continueToken) {
Object.assign(params, continueToken);
}
let res = await api.get(params);
const ent = (res.query && res.query.checkuserlog && res.query.checkuserlog.entries) || [];
if (ent.length) {
if (!results[db]) results[db] = {};
// Sum up actions per user (total and per month)
ent.forEach(e => {
const u = e.checkuser;
if (!results[db][u]) results[db][u] = { total: 0, months: {} };
const date = new Date(e.timestamp);
const mKey = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
results[db][u].total++;
results[db][u].months[mKey] = (results[db][u].months[mKey] || 0) + 1;
});
}
// Pagination check
if (res.continue && isRunning) {
continueToken = res.continue;
$('#status-msg').text(`Scanning ${db} (fetching next page)...`);
} else {
// Wiki scan finished (either no entries at all or all pages fetched)
if (!results[db]) emptyWikis.push(db);
successLocal = true;
}
} catch (err) {
const status = (err && err.xhr) ? err.xhr.status : (err && err.status);
if (status === 429) {
// Rate limit hit, wait 30s and retry the SAME wiki/page
$('#status-msg').text(`Rate limit on ${db}. Waiting 30s...`);
await sleep(30000);
} else {
// Critical error (CORS, DNS, etc.), skip this wiki
console.error(`Skipping ${db} due to error:`, err);
failedWikis.push(db);
successLocal = true;
}
}
}
$('#bar').val(i + 1);
await sleep(DELAY_MS);
}
// --- REPORT GENERATION ---
$('#status-msg').text(isRunning ? `Generating report...` : `Generating partial report...`);
const now = new Date();
const timestamp = now.getUTCFullYear() + '-' +
String(now.getUTCMonth() + 1).padStart(2, '0') + '-' +
String(now.getUTCDate()).padStart(2, '0') + ' ' +
String(now.getUTCHours()).padStart(2, '0') + ':' +
String(now.getUTCMinutes()).padStart(2, '0') + ' (UTC)';
let wt = `== Global CheckUser Stats (${mf}/${yf} - ${mt}/${yt}) ==\n`;
wt += `''Report generated on: ${timestamp}''\n\n`;
if (!isRunning) wt += `'''Note: This is a partial report (stopped manually).'''\n\n`;
else wt += `\n`;
// FIX: Define rightsLog here so it's accessible globally within the function scope
let rightsLog = `\n== Rights Log (In Period) ==\n`;
// 1. UPDATE HEADER: Added normalized columns with references
let headerMonths = monthCols.map(m => `!! ${m.label}`).join(' ');
wt += `{| class="wikitable sortable"\n! Wiki !! User (Category) !! '''Total Actions''' !! Actions per 1000 active users <ref>The count of editors with one or more edits, including on redirect pages (Source: Wikimedia Analytics API - AQS).</ref> !! Actions per 1000 edits <ref>Normalized against the total volume of all edits in the selected period (Source: Wikimedia Analytics API - AQS).</ref> ${headerMonths}\n`;
// 2. DATA PROCESSING LOOP
const sortedDBs = Object.keys(results).sort();
for (const db of sortedDBs) {
$('#status-msg').text(`Fetching metrics for ${db}...`);
const metrics = await fetchWikiMetrics(db, START, END);
await sleep(800);
const sortedUsers = Object.keys(results[db]).sort();
for (const user of sortedUsers) {
const m = await fetchUserData(user, db, START, END);
const userData = results[db][user];
// NEW: Calculate normalized statistics
let per1kUsers = "N/A";
let per1kEdits = "N/A";
if (metrics.avgEditors && metrics.avgEditors > 0) {
per1kUsers = ((userData.total / metrics.avgEditors) * 1000).toFixed(1);
}
if (metrics.totalEdits && metrics.totalEdits > 0) {
per1kEdits = ((userData.total / metrics.totalEdits) * 1000).toFixed(2);
}
let rowStr = `|-\n| ${db} || ${user} (${m.role}) || '''${userData.total}''' || ${per1kUsers} || ${per1kEdits}`;
monthCols.forEach(col => {
rowStr += ` || ${userData.months[col.key] || 0}`;
});
wt += `${rowStr}\n`;
if (m.log) {
rightsLog += `'''${user}@${db}''':${m.log}\n\n`;
}
}
}
wt += `|}\n\n== Projects with 0 actions ==\n${emptyWikis.sort().join(', ')}\n`;
if (failedWikis.length) {
wt += `\n== Errors (Failed to scan) ==\n${failedWikis.sort().join(', ')}\n`;
}
// FIX: Append the populated rightsLog to the main wikitext
wt += rightsLog;
// ADD: References section for the new <ref> tags
wt += `\n== References ==\n<references />\n`;
$('#out').val(wt).show();
$('#status-msg').text(isRunning ? `Audit complete!` : `Audit stopped. Partial report ready.`);
$('#start').prop('disabled', false);
$('#stop').prop('disabled', true);
}
setupUI();
});
})();
fgzhkm5e4yo5194mt1450cspaa8sdd4
737200
737181
2026-04-08T15:30:07Z
MrJaroslavik
44012
737200
javascript
text/javascript
// GlobalCheckUserStats.js
// Features:
// - Global Audit: Scans CheckUser logs across 900+ Wikimedia projects at once.
// - Smart Categorization: Identifies roles (Local CU, Steward, Staff, etc.).
// - Steward Logic: Detects temporary access, exact durations, and longest active periods.
// - Deep Scan: Automated pagination to bypass the 500-entry API limit (essential for enwiki).
// - Robust Connection: Automated retries for 429 rate-limits and custom domain mapping.
// - Full Reporting: Outputs sortable Wikitables and detailed rights change logs.
// - Custom UI: Integrated control panel on [[Special:BlankPage/GlobalCheckUserStats]].
// - With help of Gemini 3
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').indexOf('GlobalCheckUserStats') === -1) return;
const rawWikis = ['abstractwiki','abwiki','acewiki','adywiki','afwiki','afwikibooks','afwikiquote','afwiktionary','alswiki','altwiki','amiwiki','amwiki','amwiktionary','angwiki','angwiktionary','annwiki','anpwiki','anwiki','anwiktionary','arcwiki','arwiki','arwikibooks','arwikimedia','arwikinews','arwikiquote','arwikisource','arwikiversity','arwiktionary','arywiki','arzwiki','astwiki','astwiktionary','aswiki','aswikiquote','aswikisource','atjwiki','avkwiki','avwiki','awawiki','aywiki','aywiktionary','azbwiki','azwiki','azwikibooks','azwikiquote','azwikisource','azwiktionary','banwiki','banwikisource','barwiki','bat_smgwiki','bawiki','bawikibooks','bbcwiki','bclwiki','bclwikiquote','bclwikisource','bclwiktionary','bdrwiki','bdwikimedia','be_x_oldwiki','betawikiversity','bewiki','bewikibooks','bewikimedia','bewikiquote','bewikisource','bewiktionary','bewwiki','bewwiktionary','bgwiki','bgwikibooks','bgwikiquote','bgwikisource','bgwiktionary','bhwiki','biwiki','bjnwiki','bjnwikiquote','bjnwiktionary','blkwiki','blkwiktionary','bmwiki','bnwiki','bnwikibooks','bnwikiquote','bnwikisource','bnwikivoyage','bnwiktionary','bowiki','bpywiki','brwiki','brwikimedia','brwikiquote','brwikisource','brwiktionary','bswiki','bswikibooks','bswikinews','bswikiquote','bswikisource','bswiktionary','btmwiki','btmwiktionary','bugwiki','bxrwiki','cawiki','cawikibooks','cawikimedia','cawikinews','cawikiquote','cawikisource','cawiktionary','cbk_zamwiki','cdowiki','cebwiki','cewiki','chrwiki','chrwiktionary','chwiki','chywiki','ckbwiki','ckbwiktionary','commonswiki','cowiki','cowikimedia','cowiktionary','crhwiki','csbwiki','csbwiktionary','cswiki','cswikibooks','cswikinews','cswikiquote','cswikisource','cswikiversity','cswikivoyage','cswiktionary','cuwiki','cvwiki','cvwikibooks','cywiki','cywikibooks','cywikiquote','cywikisource','cywiktionary','dagwiki','dawiki','dawikibooks','dawikiquote','dawikisource','dawiktionary','dewiki','dewikibooks','dewikinews','dewikiquote','dewikisource','dewikiversity','dewikivoyage','dewiktionary','dgawiki','dinwiki','diqwiki','diqwiktionary','dkwikimedia','dsbwiki','dtpwiki','dtywiki','dvwiki','dvwiktionary','dzwiki','eewiki','elwiki','elwikibooks','elwikinews','elwikiquote','elwikisource','elwikiversity','elwikivoyage','elwiktionary','emlwiki','enwiki','enwikibooks','enwikinews','enwikiquote','enwikisource','enwikiversity','enwikivoyage','enwiktionary','eowiki','eowikibooks','eowikinews','eowikiquote','eowikisource','eowikivoyage','eowiktionary','eswiki','eswikibooks','eswikinews','eswikiquote','eswikisource','eswikiversity','eswikivoyage','eswiktionary','etwiki','etwikibooks','eewikimedia','etwikiquote','etwikisource','etwiktionary','euwiki','euwikibooks','euwikiquote','euwikisource','euwiktionary','extwiki','fatwiki','fawiki','fawikibooks','fawikinews','fawikiquote','fawikisource','fawikivoyage','fawiktionary','ffwiki','fiu_vrowiki','fiwiki','fiwikibooks','fiwikimedia','fiwikinews','fiwikiquote','fiwikisource','fiwikiversity','fiwikivoyage','fiwiktionary','fjwiki','fjwiktionary','fonwiki','foundationwiki','fowiki','fowikisource','fowiktionary','frpwiki','frrwiki','frwiki','frwikibooks','frwikinews','frwikiquote','frwikisource','frwikiversity','frwikivoyage','frwiktionary','furwiki','fywiki','fywikibooks','fywiktionary','gagwiki','ganwiki','gawiki','gawiktionary','gcrwiki','gdwiki','gdwiktionary','glkwiki','glwiki','glwikibooks','glwikiquote','glwikisource','glwiktionary','gnwiki','gnwiktionary','gomwiki','gomwiktionary','gorwiki','gorwikiquote','gorwiktionary','gotwiki','gpewiki','gucwiki','gurwiki','guwiki','guwikiquote','guwikisource','guwiktionary','guwwiki','guwwikinews','guwwikiquote','guwwiktionary','gvwiki','gvwiktionary','hakwiki','hawiki','hawiktionary','hawwiki','hewiki','hewikibooks','hewikinews','hewikiquote','hewikisource','hewikivoyage','hewiktionary','hifwiki','hifwiktionary','hiwiki','hiwikibooks','hiwikiquote','hiwikisource','hiwikiversity','hiwikivoyage','hiwiktionary','hrwiki','hrwikibooks','hrwikiquote','hrwikisource','hrwiktionary','hsbwiki','hsbwiktionary','htwiki','huwiki','huwikibooks','huwikisource','huwiktionary','hywiki','hywikibooks','hywikiquote','hywikisource','hywiktionary','hywwiki','iawiki','iawikibooks','iawiktionary','ibawiki','idwiki','idwikibooks','idwikiquote','idwikisource','idwikivoyage','idwiktionary','iewiki','iewiktionary','iglwiki','igwiki','igwikiquote','igwiktionary','ikwiki','ilowiki','incubatorwiki','inhwiki','iowiki','iowiktionary','iswiki','iswikibooks','iswikiquote','iswikisource','iswiktionary','itwiki','itwikibooks','itwikinews','itwikiquote','itwikisource','itwikiversity','itwikivoyage','itwiktionary','iuwiki','iuwiktionary','jamwiki','jawiki','jawikibooks','jawikinews','jawikisource','jawikiversity','jawikivoyage','jawiktionary','jbowiki','jbowiktionary','jvwiki','jvwikisource','jvwiktionary','kaawiki','kaawiktionary','kabwiki','kaiwiki','kajwiki','kawiki','kawikibooks','kawikiquote','kawikisource','kawiktionary','kbdwiki','kbdwiktionary','kbpwiki','kcgwiki','kcgwiktionary','kgewiki','kgwiki','kiwiki','kkwiki','kkwikibooks','kkwiktionary','klwiktionary','kmwiki','kmwikibooks','kmwiktionary','kncwiki','knwiki','knwikiquote','knwikisource','knwiktionary','koiwiki','kowiki','kowikibooks','kowikinews','kowikiquote','kowikisource','kowikiversity','kowiktionary','krcwiki','kshwiki','kswiki','kswiktionary','kuswiki','kuwiki','kuwikibooks','kuwikiquote','kuwiktionary','kvwiki','kwwiki','kwwiktionary','kywiki','kywikiquote','kywiktionary','labswiki','ladwiki','lawiki','lawikibooks','lawikiquote','lawikisource','lawiktionary','lbewiki','lbwiki','lbwiktionary','lezwiki','lfnwiki','lgwiki','lijwiki','lijwikisource','liwiki','liwikibooks','liwikinews','liwikiquote','liwikisource','liwiktionary','lldwiki','lmowiki','lmowiktionary','lnwiki','lnwiktionary','lowiki','lowiktionary','ltgwiki','ltwiki','ltwikibooks','ltwikiquote','ltwikisource','ltwiktionary','lvwiki','lvwiktionary','madwiki','madwikisource','madwiktionary','maiwiki','map_bmswiki','mdfwiki','mediawikiwiki','metawiki','mgwiki','mgwikibooks','mgwiktionary','mhrwiki','minwiki','minwikibooks','minwikisource','minwiktionary','miwiki','miwiktionary','mkwiki','mkwikibooks','mkwikimedia','mkwikisource','mkwiktionary','mlwiki','mlwikibooks','mlwikiquote','mlwikisource','mlwiktionary','mniwiki','mniwiktionary','mnwiki','mnwiktionary','mnwwiki','mnwwiktionary','moswiki','mrjwiki','mrwiki','mrwikibooks','mrwikiquote','mrwikisource','mrwiktionary','mswiki','mswikibooks','mswikiquote','mswikisource','mswiktionary','mtwiki','mtwiktionary','mwlwiki','mxwikimedia','myvwiki','mywiki','mywikisource','mywiktionary','mznwiki','nahwiki','nahwiktionary','napwiki','napwikisource','nawiktionary','nds_nlwiki','ndswiki','ndswiktionary','newiki','newikibooks','newiktionary','newwiki','niawiki','niawiktionary','nlwiki','nlwikibooks','nlwikimedia','nlwikinews','nlwikiquote','nlwikisource','nlwikivoyage','nlwiktionary','nnwiki','nnwikiquote','nnwiktionary','novwiki','nowiki','nowikibooks','nowikimedia','nowikinews','nowikiquote','nowikisource','nowiktionary','nqowiki','nrmwiki','nrwiki','nsowiki','nupwiki','nvwiki','nycwikimedia','nywiki','ocwiki','ocwikibooks','ocwiktionary','olowiki','omwiki','omwiktionary','orwiki','orwikisource','orwiktionary','oswiki','outreachwiki','pagwiki','pamwiki','papwiki','pawiki','pawikibooks','pawikisource','pawiktionary','pcdwiki','pcmwiki','pcmwikiquote','pdcwiki','pflwiki','piwiki','plwiki','plwikibooks','plwikimedia','plwikinews','plwikiquote','plwikisource','plwikivoyage','plwiktionary','pmswiki','pmswikisource','pnbwiki','pnbwiktionary','pntwiki','pplwiki','pswiki','pswikivoyage','pswiktionary','ptwiki','ptwikibooks','ptwikimedia','ptwikinews','ptwikiquote','ptwikisource','ptwikiversity','ptwikivoyage','ptwiktionary','pwnwiki','quwiktionary','rkiwiki','rmwiki','rmywiki','rnwiki','roa_rupwiki','roa_rupwiktionary','roa_tarawiki','rowiki','rowikibooks','rowikinews','rowikiquote','rowiktionary','rskwiki','ruewiki','ruwiki','ruwikibooks','ruwikinews','ruwikiquote','ruwikisource','ruwikiversity','ruwikivoyage','ruwiktionary','rwwiki','rwwiktionary','sahwiki','sahwikiquote','sahwikisource','satwiki','satwiktionary','sawiki','sawikibooks','sawikiquote','sawikisource','sawiktionary','scnwiki','scnwiktionary','scowiki','scwiki','sdwiki','sdwiktionary','sewiki','sewikimedia','sgwiki','sgwiktionary','shiwiki','shnwiki','shnwikibooks','shnwikinews','shnwikivoyage','shnwiktionary','shwiki','shwiktionary','shywiktionary','simplewiki','simplewiktionary','siwiki','siwikibooks','siwiktionary','skwiki','skrwiki','skrwiktionary','slwiki','slwikibooks','slwikiquote','slwikisource','slwikiversity','slwiktionary','smnwiki','smwiki','smwiktionary','snwiki','sourceswiki','sowiki','sowiktionary','specieswiki','sqwiki','sqwikibooks','sqwikinews','sqwikiquote','sqwiktionary','srnwiki','srwiki','srwikibooks','srwikinews','srwikiquote','srwikisource','srwiktionary','sswiki','sswiktionary','stqwiki','stwiki','stwiktionary','suwiki','suwikiquote','suwikisource','suwiktionary','svwiki','svwikibooks','svwikinews','svwikiquote','svwikisource','svwikiversity','svwikivoyage','svwiktionary','swwiki','swwiktionary','sylwiki','szlwiki','szywiki','tawiki','tawikibooks','tawikinews','tawikiquote','tawikisource','tawiktionary','taywiki','tcywiki','tcywikisource','tcywiktionary','tddwiki','tewiki','tewikibooks','tewikiquote','tewikisource','tewiktionary','tgwiki','tgwikibooks','tgwiktionary','thwiki','thwikibooks','thwikimedia','thwikiquote','thwikisource','thwiktionary','tigwiki','tiwiki','tiwiktionary','tkwiki','tkwiktionary','tlwiki','tlwikibooks','tlwikiquote','tlwikisource','tlwiktionary','tlywiki','tnwiki','tnwiktionary','tokwiki','towiki','tpiwiki','tpiwiktionary','trvwiki','trwiki','trwikibooks','trwikimedia','trwikinews','trwikiquote','trwikisource','trwikivoyage','trwiktionary','tswiki','tswiktionary','ttwiki','ttwikibooks','ttwikiquote','ttwiktionary','tumwiki','twwiki','twwiktionary','tyvwiki','tywiki','uawikimedia','udmwiki','ugwiki','ugwiktionary','ukwiki','ukwikibooks','ukwikinews','ukwikiquote','ukwikisource','ukwikivoyage','ukwiktionary','urwiki','urwikibooks','urwikiquote','urwikisource','urwiktionary','uzwiki','uzwikiquote','uzwiktionary','vecwiki','vecwikisource','vecwiktionary','vepwiki','vewiki','viwiki','viwikibooks','viwikiquote','viwikisource','viwikivoyage','viwiktionary','vlswiki','vowiki','vowiktionary','warwiki','wawiki','wawikisource','wawiktionary','wikidatawiki','wikimaniawiki','wowiki','wowiktionary','wuuwiki','xalwiki','xhwiki','xmfwiki','yiwiki','yiwikisource','yiwiktionary','yowiki','zawiki','zeawiki','zghwiki','zghwiktionary','zh_classicalwiki','zh_min_nanwiki','zh_min_nanwikisource','zh_min_nanwiktionary','zh_yuewiki','zhwiki','zhwikibooks','zhwikinews','zhwikiquote','zhwikisource','zhwikiversity','zhwikivoyage','zhwiktionary','zuwiki','zuwiktionary','test2wiki','testwiki'];
const DELAY_MS = 800; // Safe balanced speed
const sleep = ms => new Promise(r => setTimeout(r, ms));
let userCache = {};
let historyCache = {}; // Added this to prevent the ReferenceError
function getDomain(db) {
const mapping = {
'commonswiki': 'commons.wikimedia.org', 'metawiki': 'meta.wikimedia.org', 'wikidatawiki': 'www.wikidata.org',
'mediawikiwiki': 'www.mediawiki.org', 'sourceswiki': 'wikisource.org', 'foundationwiki': 'foundation.wikimedia.org',
'incubatorwiki': 'incubator.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org',
'be_x_oldwiki': 'be-tarask.wikipedia.org', 'labswiki': 'wikitech.wikimedia.org',
'wikimaniawiki': 'wikimania.wikimedia.org', 'specieswiki': 'species.wikimedia.org',
'abstractwiki': 'abstract.wikipedia.org', 'betawikiversity': 'beta.wikiversity.org',
'mo_wiki': 'ro.wikipedia.org',
'testwiki': 'test.wikipedia.org', 'test2wiki': 'test2.wikipedia.org',
'wikifunctionswiki': 'www.wikifunctions.org',
'testcommonswiki': 'test-commons.wikimedia.org',
'testwikidatawiki': 'test.wikidata.org',
};
if (mapping[db]) return mapping[db];
const name = db.replace(/_/g, '-');
if (name.endsWith('wikisource')) return name.slice(0, -10) + '.wikisource.org';
if (name.endsWith('wikiversity')) return name.slice(0, -11) + '.wikiversity.org';
if (name.endsWith('wiktionary')) return name.slice(0, -10) + '.wiktionary.org';
if (name.endsWith('wikivoyage')) return name.slice(0, -10) + '.wikivoyage.org';
if (name.endsWith('wikibooks')) return name.slice(0, -9) + '.wikibooks.org';
if (name.endsWith('wikiquote')) return name.slice(0, -9) + '.wikiquote.org';
if (name.endsWith('wikinews')) return name.slice(0, -8) + '.wikinews.org';
if (name.endsWith('wikimedia')) return name.slice(0, -9) + '.wikimedia.org';
if (name.endsWith('wiki')) return name.slice(0, -4) + '.wikipedia.org';
return name + '.wikipedia.org';
}
mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi']).then(function() {
const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { anonymous: true });
const now = new Date();
let results = {}, emptyWikis = [], failedWikis = [], isRunning = false;
function setupUI() {
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
// CHANGE: Dynamically generate dropdown options for years and months
let yearOpts = '';
for (let y = currentYear; y >= 2005; y--) {
yearOpts += `<option value="${y}">${y}</option>`;
}
let monthOptsFrom = '';
let monthOptsTo = '';
for (let m = 1; m <= 12; m++) {
monthOptsFrom += `<option value="${m}" ${m === 1 ? 'selected' : ''}>${m}</option>`;
monthOptsTo += `<option value="${m}" ${m === currentMonth ? 'selected' : ''}>${m}</option>`;
}
$('#firstHeading').text('GlobalCheckUserStats.js');
$('#mw-content-text').empty().append(`
<div style="border:1px solid #a2a9b1; padding:15px; background:#f8f9fa;">
<h3>Audit Range</h3>
From:
<select id="y-f" style="width:70px;">${yearOpts}</select>
<select id="m-f" style="width:50px;">${monthOptsFrom}</select>
To:
<select id="y-t" style="width:70px;">${yearOpts}</select>
<select id="m-t" style="width:50px;">${monthOptsTo}</select>
<button id="start" class="mw-ui-button mw-ui-progressive" style="margin-left:15px;">Run GlobalCheckUserStats.js</button>
<button id="stop" class="mw-ui-button mw-ui-destructive" disabled>Stop</button>
<div id="status-msg" style="margin-top:10px; font-weight:bold; color:#0056b3;">Ready.</div>
<div style="margin-top:5px;"><progress id="bar" value="0" max="${rawWikis.length}" style="width:100%"></progress></div>
<textarea id="out" style="width:100%; height:450px; margin-top:10px; display:none; font-family:monospace; font-size:12px;"></textarea>
</div>
`);
$('#start').click(() => runAudit($('#y-f').val(), $('#m-f').val(), $('#y-t').val(), $('#m-t').val()));
$('#stop').click(() => isRunning = false);
}
// NEW: Fetch monthly averaged editors and total edits from Wikimedia Analytics API
async function fetchWikiMetrics(db, start, end) {
const project = getDomain(db);
const aqsStart = start.split('T')[0].replace(/-/g, '');
const aqsEnd = end.split('T')[0].replace(/-/g, '');
const editorsUrl = `https://wikimedia.org/api/rest_v1/metrics/editors/aggregate/${project}/all-editor-types/all-page-types/all-activity-levels/monthly/${aqsStart}/${aqsEnd}`;
const editsUrl = `https://wikimedia.org/api/rest_v1/metrics/edits/aggregate/${project}/all-editor-types/all-page-types/monthly/${aqsStart}/${aqsEnd}`;
const fetchWithRetry = async (url) => {
let attempts = 0;
while (attempts < 5) {
try {
const res = await fetch(url);
if (res.status === 429) {
let wait = (attempts + 1) * 7000;
$('#status-msg').text(`AQS limit! Cooling down ${wait/1000}s...`);
await sleep(wait);
attempts++;
continue;
}
if (!res.ok) return null;
return await res.json();
} catch (e) {
attempts++;
await sleep(2000);
}
}
return null;
};
try {
const [edRes, etRes] = await Promise.all([
fetchWithRetry(editorsUrl),
fetchWithRetry(editsUrl)
]);
if (!edRes?.items?.[0]?.results || !etRes?.items?.[0]?.results) {
return { avgEditors: 0, totalEdits: 0 };
}
const editorsResults = edRes?.items?.[0]?.results || [];
const avgEditors = editorsResults.length > 0
? editorsResults.reduce((sum, m) => sum + m.editors, 0) / editorsResults.length
: 0;
const totalEdits = etRes?.items?.[0]?.results
? etRes.items[0].results.reduce((sum, m) => sum + m.edits, 0)
: 0;
return { avgEditors, totalEdits };
} catch (e) {
return { avgEditors: 0, totalEdits: 0 };
}
}
// Helper function to check global rights history on Meta-Wiki
async function checkGlobalHistory(user, start, end) {
try {
const res = await metaApi.get({
action: 'query',
list: 'logevents',
letype: 'gblrights',
letitle: 'User:' + user,
formatversion: 2
});
const logs = res.query.logevents || [];
const auditStart = new Date(start);
const auditEnd = new Date(end);
let wasSteward = false, wasStaff = false, wasOmbuds = false;
for (const e of logs) {
const ts = new Date(e.timestamp);
const p = e.params || {};
const added = p.newGroups || p[1] || [];
const removed = p.oldGroups || p[0] || [];
// If a removal happened AFTER the audit started, the user held the rights during the period
if (ts > auditStart) {
if (removed.includes('steward')) wasSteward = true;
if (removed.includes('staff')) wasStaff = true;
if (removed.includes('ombuds')) wasOmbuds = true;
}
// If an addition happened BEFORE the audit ended, the user held the rights during the period
if (ts < auditEnd) {
if (added.includes('steward')) wasSteward = true;
if (added.includes('staff')) wasStaff = true;
if (added.includes('ombuds')) wasOmbuds = true;
}
}
return { wasSteward, wasStaff, wasOmbuds };
} catch (e) {
// Fallback for API errors
return { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
async function fetchUserData(user, db, start, end) {
// Priority 0: Use cache for current global groups
if (!userCache[user]) {
try {
const gres = await metaApi.get({ action: 'query', meta: 'globaluserinfo', guiprop: 'groups', guiuser: user, formatversion: 2 });
userCache[user] = gres.query.globaluserinfo.groups || [];
} catch(e) { userCache[user] = []; }
}
const gGroups = userCache[user];
const isGloballyStaff = gGroups.includes('staff');
const isGloballySteward = gGroups.includes('steward');
const isGloballyOmbuds = gGroups.includes('ombuds');
// Check current local CheckUser rights
let isCurrentLocal = false;
try {
const localApi = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
const ures = await localApi.get({ action: 'query', list: 'users', ususers: user, usprop: 'groups', formatversion: 2 });
const lGroups = (ures.query.users[0] && ures.query.users[0].groups) || [];
isCurrentLocal = lGroups.includes('checkuser');
} catch(e) { /* API failure fallback */ }
// Rights log analysis for the specific Wiki
const target = 'User:' + user + '@' + db;
let logText = ''; // Default is empty, filled only if there are logs
let addTime = null, removeTime = null, isSelfAssign = false;
// CHANGE: Variables to track the longest duration and the number of actions
let maxDurationMins = -1;
let longestTimeStr = "";
let assignCount = 0;
try {
const res = await metaApi.get({
action: 'query', list: 'logevents', letype: 'rights', letitle: target,
lestart: end, leend: start, ledir: 'older',
lelimit: 'max', // ADDED: Loads up to 500 logs instead of 10
formatversion: 2
});
const events = res.query.logevents || [];
let filtered = [];
// Variables to pair removal and addition (iterating newest to oldest)
let tempRemoveTime = null;
let tempRemoveTimeStr = "Not removed";
events.forEach(e => {
const p = e.params || {};
const cuAdded = (p.add || []).includes('checkuser') || ((p.newgroups || []).includes('checkuser') && !(p.oldgroups || []).includes('checkuser'));
const cuRemoved = (p.remove || []).includes('checkuser') || (!(p.newgroups || []).includes('checkuser') && (p.oldgroups || []).includes('checkuser'));
const exactTimeLog = e.timestamp.replace('T', ' ').replace('Z', '');
if (cuRemoved) {
tempRemoveTime = new Date(e.timestamp);
tempRemoveTimeStr = exactTimeLog;
// Save the most recent removal for the overall table role categorization
if (!removeTime) {
removeTime = tempRemoveTime;
if (e.user === user || !e.user) isSelfAssign = true;
}
}
if (cuAdded) {
const currentAddTime = new Date(e.timestamp);
let metadataRemoveTime = tempRemoveTime;
let metadataRemoveTimeStr = tempRemoveTimeStr;
// Save the most recent addition for the overall table role categorization
if (!addTime) {
addTime = currentAddTime;
isSelfAssign = (e.user === user);
}
// Fallback: Check expiry in metadata if explicit removal log is missing
const metadata = p.newmetadata || [];
const cuMeta = metadata.find(m => m.group === 'checkuser');
if (cuMeta && cuMeta.expiry && cuMeta.expiry !== 'infinity' && tempRemoveTimeStr === "Not removed") {
metadataRemoveTime = new Date(cuMeta.expiry);
metadataRemoveTimeStr = cuMeta.expiry.replace('T', ' ').replace('Z', '');
if (!removeTime) removeTime = metadataRemoveTime;
}
// Calculate duration for this specific 1-line pair
let pairDuration = "Unknown";
if (metadataRemoveTime) {
const diffMs = Math.abs(metadataRemoveTime - currentAddTime);
const diffMins = Math.round(diffMs / 60000); // CHANGE: Rounds to the nearest whole minute
const days = Math.floor(diffMins / 1440);
const hours = Math.floor((diffMins % 1440) / 60);
const mins = diffMins % 60;
if (days > 0) pairDuration = `${days}d ${hours}h ${mins}m`;
else if (hours > 0) pairDuration = `${hours}h ${mins}m`;
else if (mins > 0) pairDuration = `${mins}m`;
else pairDuration = "<1m";
// CHANGE: Track the total number of assignments and store the absolute longest duration
assignCount++;
if (diffMins > maxDurationMins) {
maxDurationMins = diffMins;
longestTimeStr = pairDuration;
}
}
// Push the combined 1-line format to the log using commas
filtered.push(`* Added: ${exactTimeLog}, Removed: ${metadataRemoveTimeStr}, Duration: ${pairDuration}`);
// Reset pair variables for the next older event in the loop
tempRemoveTime = null;
tempRemoveTimeStr = "Not removed";
}
});
// Edge case: If a removal was found but the addition happened before the audit period
if (tempRemoveTime && tempRemoveTimeStr !== "Not removed") {
filtered.push(`* Added: Before audit period, Removed: ${tempRemoveTimeStr}, Duration: Unknown`);
}
// Joins logs with newlines for the report
if (filtered.length) {
logText = '\n' + filtered.join('\n');
}
} catch (err) { /* Rights log query failed */ }
// --- GLOBAL HISTORY CHECK (WITH CACHE) ---
if (!historyCache[user]) {
if (!isGloballySteward || !isGloballyStaff || !isGloballyOmbuds) {
// Check Meta-Wiki history logs only if user is missing at least one global role today
historyCache[user] = await checkGlobalHistory(user, start, end);
} else {
historyCache[user] = { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
const history = historyCache[user];
// --- HIERARCHY LOGIC (CheckUser Audit Focus) ---
let roleLabel = "";
// 1. STAFF (Highest audit priority for WMF accountability)
if (isGloballyStaff || history.wasStaff) {
roleLabel = isGloballyStaff ? "Current Staff" : "Former Staff (in period)";
}
// 2. LOCAL CHECKUSER (Overrides Steward/Ombudsman roles if local rights exist)
else if (isCurrentLocal) {
roleLabel = "Current Local CheckUser";
}
// 3. STEWARD - SPECIFIC INTERVENTION (Using exact self-assignment duration or max duration if multiple)
else if (longestTimeStr && isSelfAssign) {
if (assignCount > 1) {
roleLabel = `Steward action (Self-assign: longest ${longestTimeStr}, see log)`;
} else {
roleLabel = `Steward action (Self-assign: ${longestTimeStr})`;
}
}
// 4. STEWARD - GENERAL (Global rights usage on external wikis)
else if (isGloballySteward || history.wasSteward) {
roleLabel = isGloballySteward ? "Current Steward" : "Former Steward (in period)";
}
// 5. OMBUDSMAN (Global oversight role)
else if (isGloballyOmbuds || history.wasOmbuds) {
roleLabel = isGloballyOmbuds ? "Current Ombudsman" : "Former Ombudsman (in period)";
}
// 6. FORMER LOCAL CheckUser (Fallback for those who lost rights but performed actions)
else {
roleLabel = "Former Local CheckUser";
}
return { role: roleLabel, log: logText };
}
async function runAudit(yf, mf, yt, mt) {
isRunning = true;
results = {};
emptyWikis = [];
failedWikis = [];
userCache = {};
historyCache = {};
$('#start').prop('disabled', true);
$('#stop').prop('disabled', false);
$('#out').hide();
// Format ISO timestamps for the API
const START = `${yf}-${mf.toString().padStart(2, '0')}-01T00:00:00Z`;
const lastDay = new Date(Date.UTC(yt, mt, 0)).getUTCDate();
const END = `${yt}-${mt.toString().padStart(2, '0')}-${lastDay.toString().padStart(2, '0')}T23:59:59Z`;
// Generate list of months for dynamic table columns
const monthCols = [];
let currY = parseInt(yf), currM = parseInt(mf);
const endY = parseInt(yt), endM = parseInt(mt);
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
while (currY < endY || (currY === endY && currM <= endM)) {
monthCols.push({
key: `${currY}-${String(currM).padStart(2, '0')}`,
label: `${monthNames[currM - 1]} ${currY}` // e.g., "Jan 2024"
});
currM++;
if (currM > 12) { currM = 1; currY++; }
}
for (let i = 0; i < rawWikis.length && isRunning; i++) {
const db = rawWikis[i];
$('#status-msg').text(`Scanning ${db} (${i + 1}/${rawWikis.length})...`);
let successLocal = false;
let continueToken = null; // Token for pagination
while (!successLocal && isRunning) {
try {
const api = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
let params = {
action: 'query',
list: 'checkuserlog',
culfrom: START,
culto: END,
culdir: 'newer',
cullimit: 'max',
formatversion: 2
};
// Add continue token if we are on page 2 or further
if (continueToken) {
Object.assign(params, continueToken);
}
let res = await api.get(params);
const ent = (res.query && res.query.checkuserlog && res.query.checkuserlog.entries) || [];
if (ent.length) {
if (!results[db]) results[db] = {};
// Sum up actions per user (total and per month)
ent.forEach(e => {
const u = e.checkuser;
if (!results[db][u]) results[db][u] = { total: 0, months: {} };
const date = new Date(e.timestamp);
const mKey = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
results[db][u].total++;
results[db][u].months[mKey] = (results[db][u].months[mKey] || 0) + 1;
});
}
// Pagination check
if (res.continue && isRunning) {
continueToken = res.continue;
$('#status-msg').text(`Scanning ${db} (fetching next page)...`);
} else {
// Wiki scan finished (either no entries at all or all pages fetched)
if (!results[db]) emptyWikis.push(db);
successLocal = true;
}
} catch (err) {
const status = (err && err.xhr) ? err.xhr.status : (err && err.status);
if (status === 429) {
// Rate limit hit, wait 30s and retry the SAME wiki/page
$('#status-msg').text(`Rate limit on ${db}. Waiting 30s...`);
await sleep(30000);
} else {
// Critical error (CORS, DNS, etc.), skip this wiki
console.error(`Skipping ${db} due to error:`, err);
failedWikis.push(db);
successLocal = true;
}
}
}
$('#bar').val(i + 1);
await sleep(DELAY_MS);
}
// --- REPORT GENERATION ---
$('#status-msg').text(isRunning ? `Generating report...` : `Generating partial report...`);
const now = new Date();
const timestamp = now.getUTCFullYear() + '-' +
String(now.getUTCMonth() + 1).padStart(2, '0') + '-' +
String(now.getUTCDate()).padStart(2, '0') + ' ' +
String(now.getUTCHours()).padStart(2, '0') + ':' +
String(now.getUTCMinutes()).padStart(2, '0') + ' (UTC)';
let wt = `== Global CheckUser Stats (${mf}/${yf} - ${mt}/${yt}) ==\n`;
wt += `''Report generated on: ${timestamp}''\n\n`;
if (!isRunning) wt += `'''Note: This is a partial report (stopped manually).'''\n\n`;
else wt += `\n`;
// FIX: Define rightsLog here so it's accessible globally within the function scope
let rightsLog = `\n== Rights Log (In Period) ==\n`;
// 1. UPDATE HEADER: Added normalized columns with references
let headerMonths = monthCols.map(m => `!! ${m.label}`).join(' ');
wt += `{| class="wikitable sortable"\n! Wiki !! User (Category) !! '''Total Actions''' !! Actions per 1000 active users <ref>The count of editors with one or more edits, including on redirect pages (Source: Wikimedia Analytics API - AQS).</ref> !! Actions per 1000 edits <ref>Normalized against the total volume of all edits in the selected period (Source: Wikimedia Analytics API - AQS).</ref> ${headerMonths}\n`;
// 2. DATA PROCESSING LOOP
const sortedDBs = Object.keys(results).sort();
for (const db of sortedDBs) {
await sleep(2500);
$('#status-msg').text(`Fetching metrics for ${db}...`);
const metrics = await fetchWikiMetrics(db, START, END);
const sortedUsers = Object.keys(results[db]).sort();
for (const user of sortedUsers) {
const m = await fetchUserData(user, db, START, END);
const userData = results[db][user];
// NEW: Calculate normalized statistics
let per1kUsers = "N/A";
let per1kEdits = "N/A";
if (metrics.avgEditors && metrics.avgEditors > 0) {
per1kUsers = ((userData.total / metrics.avgEditors) * 1000).toFixed(1);
}
if (metrics.totalEdits && metrics.totalEdits > 0) {
per1kEdits = ((userData.total / metrics.totalEdits) * 1000).toFixed(2);
}
let rowStr = `|-\n| ${db} || ${user} (${m.role}) || '''${userData.total}''' || ${per1kUsers} || ${per1kEdits}`;
monthCols.forEach(col => {
rowStr += ` || ${userData.months[col.key] || 0}`;
});
wt += `${rowStr}\n`;
if (m.log) {
rightsLog += `'''${user}@${db}''':${m.log}\n\n`;
}
}
}
wt += `|}\n\n== Projects with 0 actions ==\n${emptyWikis.sort().join(', ')}\n`;
if (failedWikis.length) {
wt += `\n== Errors (Failed to scan) ==\n${failedWikis.sort().join(', ')}\n`;
}
// FIX: Append the populated rightsLog to the main wikitext
wt += rightsLog;
// ADD: References section for the new <ref> tags
wt += `\n== References ==\n<references />\n`;
$('#out').val(wt).show();
$('#status-msg').text(isRunning ? `Audit complete!` : `Audit stopped. Partial report ready.`);
$('#start').prop('disabled', false);
$('#stop').prop('disabled', true);
}
setupUI();
});
})();
ld88zprjv5rb33864k2j8vs9tjd8urv
737210
737200
2026-04-08T17:32:28Z
MrJaroslavik
44012
e
737210
javascript
text/javascript
// GlobalCheckUserStats.js
// Features:
// - Global Audit: Scans CheckUser logs across 900+ Wikimedia projects at once.
// - Smart Categorization: Identifies roles (Local CU, Steward, Staff, etc.).
// - Steward Logic: Detects temporary access, exact durations, and longest active periods.
// - Deep Scan: Automated pagination to bypass the 500-entry API limit (essential for enwiki).
// - Robust Connection: Automated retries for 429 rate-limits and custom domain mapping.
// - Full Reporting: Outputs sortable Wikitables and detailed rights change logs.
// - Custom UI: Integrated control panel on [[Special:BlankPage/GlobalCheckUserStats]].
// - With help of Gemini 3
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').indexOf('GlobalCheckUserStats') === -1) return;
const rawWikis = ['abstractwiki','abwiki','acewiki','adywiki','afwiki','afwikibooks','afwikiquote','afwiktionary','alswiki','altwiki','amiwiki','amwiki','amwiktionary','angwiki','angwiktionary','annwiki','anpwiki','anwiki','anwiktionary','arcwiki','arwiki','arwikibooks','arwikimedia','arwikinews','arwikiquote','arwikisource','arwikiversity','arwiktionary','arywiki','arzwiki','astwiki','astwiktionary','aswiki','aswikiquote','aswikisource','atjwiki','avkwiki','avwiki','awawiki','aywiki','aywiktionary','azbwiki','azwiki','azwikibooks','azwikiquote','azwikisource','azwiktionary','banwiki','banwikisource','barwiki','bat_smgwiki','bawiki','bawikibooks','bbcwiki','bclwiki','bclwikiquote','bclwikisource','bclwiktionary','bdrwiki','bdwikimedia','be_x_oldwiki','betawikiversity','bewiki','bewikibooks','bewikimedia','bewikiquote','bewikisource','bewiktionary','bewwiki','bewwiktionary','bgwiki','bgwikibooks','bgwikiquote','bgwikisource','bgwiktionary','bhwiki','biwiki','bjnwiki','bjnwikiquote','bjnwiktionary','blkwiki','blkwiktionary','bmwiki','bnwiki','bnwikibooks','bnwikiquote','bnwikisource','bnwikivoyage','bnwiktionary','bowiki','bpywiki','brwiki','brwikimedia','brwikiquote','brwikisource','brwiktionary','bswiki','bswikibooks','bswikinews','bswikiquote','bswikisource','bswiktionary','btmwiki','btmwiktionary','bugwiki','bxrwiki','cawiki','cawikibooks','cawikimedia','cawikinews','cawikiquote','cawikisource','cawiktionary','cbk_zamwiki','cdowiki','cebwiki','cewiki','chrwiki','chrwiktionary','chwiki','chywiki','ckbwiki','ckbwiktionary','commonswiki','cowiki','cowikimedia','cowiktionary','crhwiki','csbwiki','csbwiktionary','cswiki','cswikibooks','cswikinews','cswikiquote','cswikisource','cswikiversity','cswikivoyage','cswiktionary','cuwiki','cvwiki','cvwikibooks','cywiki','cywikibooks','cywikiquote','cywikisource','cywiktionary','dagwiki','dawiki','dawikibooks','dawikiquote','dawikisource','dawiktionary','dewiki','dewikibooks','dewikinews','dewikiquote','dewikisource','dewikiversity','dewikivoyage','dewiktionary','dgawiki','dinwiki','diqwiki','diqwiktionary','dkwikimedia','dsbwiki','dtpwiki','dtywiki','dvwiki','dvwiktionary','dzwiki','eewiki','elwiki','elwikibooks','elwikinews','elwikiquote','elwikisource','elwikiversity','elwikivoyage','elwiktionary','emlwiki','enwiki','enwikibooks','enwikinews','enwikiquote','enwikisource','enwikiversity','enwikivoyage','enwiktionary','eowiki','eowikibooks','eowikinews','eowikiquote','eowikisource','eowikivoyage','eowiktionary','eswiki','eswikibooks','eswikinews','eswikiquote','eswikisource','eswikiversity','eswikivoyage','eswiktionary','etwiki','etwikibooks','eewikimedia','etwikiquote','etwikisource','etwiktionary','euwiki','euwikibooks','euwikiquote','euwikisource','euwiktionary','extwiki','fatwiki','fawiki','fawikibooks','fawikinews','fawikiquote','fawikisource','fawikivoyage','fawiktionary','ffwiki','fiu_vrowiki','fiwiki','fiwikibooks','fiwikimedia','fiwikinews','fiwikiquote','fiwikisource','fiwikiversity','fiwikivoyage','fiwiktionary','fjwiki','fjwiktionary','fonwiki','foundationwiki','fowiki','fowikisource','fowiktionary','frpwiki','frrwiki','frwiki','frwikibooks','frwikinews','frwikiquote','frwikisource','frwikiversity','frwikivoyage','frwiktionary','furwiki','fywiki','fywikibooks','fywiktionary','gagwiki','ganwiki','gawiki','gawiktionary','gcrwiki','gdwiki','gdwiktionary','glkwiki','glwiki','glwikibooks','glwikiquote','glwikisource','glwiktionary','gnwiki','gnwiktionary','gomwiki','gomwiktionary','gorwiki','gorwikiquote','gorwiktionary','gotwiki','gpewiki','gucwiki','gurwiki','guwiki','guwikiquote','guwikisource','guwiktionary','guwwiki','guwwikinews','guwwikiquote','guwwiktionary','gvwiki','gvwiktionary','hakwiki','hawiki','hawiktionary','hawwiki','hewiki','hewikibooks','hewikinews','hewikiquote','hewikisource','hewikivoyage','hewiktionary','hifwiki','hifwiktionary','hiwiki','hiwikibooks','hiwikiquote','hiwikisource','hiwikiversity','hiwikivoyage','hiwiktionary','hrwiki','hrwikibooks','hrwikiquote','hrwikisource','hrwiktionary','hsbwiki','hsbwiktionary','htwiki','huwiki','huwikibooks','huwikisource','huwiktionary','hywiki','hywikibooks','hywikiquote','hywikisource','hywiktionary','hywwiki','iawiki','iawikibooks','iawiktionary','ibawiki','idwiki','idwikibooks','idwikiquote','idwikisource','idwikivoyage','idwiktionary','iewiki','iewiktionary','iglwiki','igwiki','igwikiquote','igwiktionary','ikwiki','ilowiki','incubatorwiki','inhwiki','iowiki','iowiktionary','iswiki','iswikibooks','iswikiquote','iswikisource','iswiktionary','itwiki','itwikibooks','itwikinews','itwikiquote','itwikisource','itwikiversity','itwikivoyage','itwiktionary','iuwiki','iuwiktionary','jamwiki','jawiki','jawikibooks','jawikinews','jawikisource','jawikiversity','jawikivoyage','jawiktionary','jbowiki','jbowiktionary','jvwiki','jvwikisource','jvwiktionary','kaawiki','kaawiktionary','kabwiki','kaiwiki','kajwiki','kawiki','kawikibooks','kawikiquote','kawikisource','kawiktionary','kbdwiki','kbdwiktionary','kbpwiki','kcgwiki','kcgwiktionary','kgewiki','kgwiki','kiwiki','kkwiki','kkwikibooks','kkwiktionary','klwiktionary','kmwiki','kmwikibooks','kmwiktionary','kncwiki','knwiki','knwikiquote','knwikisource','knwiktionary','koiwiki','kowiki','kowikibooks','kowikinews','kowikiquote','kowikisource','kowikiversity','kowiktionary','krcwiki','kshwiki','kswiki','kswiktionary','kuswiki','kuwiki','kuwikibooks','kuwikiquote','kuwiktionary','kvwiki','kwwiki','kwwiktionary','kywiki','kywikiquote','kywiktionary','labswiki','ladwiki','lawiki','lawikibooks','lawikiquote','lawikisource','lawiktionary','lbewiki','lbwiki','lbwiktionary','lezwiki','lfnwiki','lgwiki','lijwiki','lijwikisource','liwiki','liwikibooks','liwikinews','liwikiquote','liwikisource','liwiktionary','lldwiki','lmowiki','lmowiktionary','lnwiki','lnwiktionary','lowiki','lowiktionary','ltgwiki','ltwiki','ltwikibooks','ltwikiquote','ltwikisource','ltwiktionary','lvwiki','lvwiktionary','madwiki','madwikisource','madwiktionary','maiwiki','map_bmswiki','mdfwiki','mediawikiwiki','metawiki','mgwiki','mgwikibooks','mgwiktionary','mhrwiki','minwiki','minwikibooks','minwikisource','minwiktionary','miwiki','miwiktionary','mkwiki','mkwikibooks','mkwikimedia','mkwikisource','mkwiktionary','mlwiki','mlwikibooks','mlwikiquote','mlwikisource','mlwiktionary','mniwiki','mniwiktionary','mnwiki','mnwiktionary','mnwwiki','mnwwiktionary','moswiki','mrjwiki','mrwiki','mrwikibooks','mrwikiquote','mrwikisource','mrwiktionary','mswiki','mswikibooks','mswikiquote','mswikisource','mswiktionary','mtwiki','mtwiktionary','mwlwiki','mxwikimedia','myvwiki','mywiki','mywikisource','mywiktionary','mznwiki','nahwiki','nahwiktionary','napwiki','napwikisource','nawiktionary','nds_nlwiki','ndswiki','ndswiktionary','newiki','newikibooks','newiktionary','newwiki','niawiki','niawiktionary','nlwiki','nlwikibooks','nlwikimedia','nlwikinews','nlwikiquote','nlwikisource','nlwikivoyage','nlwiktionary','nnwiki','nnwikiquote','nnwiktionary','novwiki','nowiki','nowikibooks','nowikimedia','nowikinews','nowikiquote','nowikisource','nowiktionary','nqowiki','nrmwiki','nrwiki','nsowiki','nupwiki','nvwiki','nycwikimedia','nywiki','ocwiki','ocwikibooks','ocwiktionary','olowiki','omwiki','omwiktionary','orwiki','orwikisource','orwiktionary','oswiki','outreachwiki','pagwiki','pamwiki','papwiki','pawiki','pawikibooks','pawikisource','pawiktionary','pcdwiki','pcmwiki','pcmwikiquote','pdcwiki','pflwiki','piwiki','plwiki','plwikibooks','plwikimedia','plwikinews','plwikiquote','plwikisource','plwikivoyage','plwiktionary','pmswiki','pmswikisource','pnbwiki','pnbwiktionary','pntwiki','pplwiki','pswiki','pswikivoyage','pswiktionary','ptwiki','ptwikibooks','ptwikimedia','ptwikinews','ptwikiquote','ptwikisource','ptwikiversity','ptwikivoyage','ptwiktionary','pwnwiki','quwiktionary','rkiwiki','rmwiki','rmywiki','rnwiki','roa_rupwiki','roa_rupwiktionary','roa_tarawiki','rowiki','rowikibooks','rowikinews','rowikiquote','rowiktionary','rskwiki','ruewiki','ruwiki','ruwikibooks','ruwikinews','ruwikiquote','ruwikisource','ruwikiversity','ruwikivoyage','ruwiktionary','rwwiki','rwwiktionary','sahwiki','sahwikiquote','sahwikisource','satwiki','satwiktionary','sawiki','sawikibooks','sawikiquote','sawikisource','sawiktionary','scnwiki','scnwiktionary','scowiki','scwiki','sdwiki','sdwiktionary','sewiki','sewikimedia','sgwiki','sgwiktionary','shiwiki','shnwiki','shnwikibooks','shnwikinews','shnwikivoyage','shnwiktionary','shwiki','shwiktionary','shywiktionary','simplewiki','simplewiktionary','siwiki','siwikibooks','siwiktionary','skwiki','skrwiki','skrwiktionary','slwiki','slwikibooks','slwikiquote','slwikisource','slwikiversity','slwiktionary','smnwiki','smwiki','smwiktionary','snwiki','sourceswiki','sowiki','sowiktionary','specieswiki','sqwiki','sqwikibooks','sqwikinews','sqwikiquote','sqwiktionary','srnwiki','srwiki','srwikibooks','srwikinews','srwikiquote','srwikisource','srwiktionary','sswiki','sswiktionary','stqwiki','stwiki','stwiktionary','suwiki','suwikiquote','suwikisource','suwiktionary','svwiki','svwikibooks','svwikinews','svwikiquote','svwikisource','svwikiversity','svwikivoyage','svwiktionary','swwiki','swwiktionary','sylwiki','szlwiki','szywiki','tawiki','tawikibooks','tawikinews','tawikiquote','tawikisource','tawiktionary','taywiki','tcywiki','tcywikisource','tcywiktionary','tddwiki','tewiki','tewikibooks','tewikiquote','tewikisource','tewiktionary','tgwiki','tgwikibooks','tgwiktionary','thwiki','thwikibooks','thwikimedia','thwikiquote','thwikisource','thwiktionary','tigwiki','tiwiki','tiwiktionary','tkwiki','tkwiktionary','tlwiki','tlwikibooks','tlwikiquote','tlwikisource','tlwiktionary','tlywiki','tnwiki','tnwiktionary','tokwiki','towiki','tpiwiki','tpiwiktionary','trvwiki','trwiki','trwikibooks','trwikimedia','trwikinews','trwikiquote','trwikisource','trwikivoyage','trwiktionary','tswiki','tswiktionary','ttwiki','ttwikibooks','ttwikiquote','ttwiktionary','tumwiki','twwiki','twwiktionary','tyvwiki','tywiki','uawikimedia','udmwiki','ugwiki','ugwiktionary','ukwiki','ukwikibooks','ukwikinews','ukwikiquote','ukwikisource','ukwikivoyage','ukwiktionary','urwiki','urwikibooks','urwikiquote','urwikisource','urwiktionary','uzwiki','uzwikiquote','uzwiktionary','vecwiki','vecwikisource','vecwiktionary','vepwiki','vewiki','viwiki','viwikibooks','viwikiquote','viwikisource','viwikivoyage','viwiktionary','vlswiki','vowiki','vowiktionary','warwiki','wawiki','wawikisource','wawiktionary','wikidatawiki','wikimaniawiki','wowiki','wowiktionary','wuuwiki','xalwiki','xhwiki','xmfwiki','yiwiki','yiwikisource','yiwiktionary','yowiki','zawiki','zeawiki','zghwiki','zghwiktionary','zh_classicalwiki','zh_min_nanwiki','zh_min_nanwikisource','zh_min_nanwiktionary','zh_yuewiki','zhwiki','zhwikibooks','zhwikinews','zhwikiquote','zhwikisource','zhwikiversity','zhwikivoyage','zhwiktionary','zuwiki','zuwiktionary','test2wiki','testwiki'];
const DELAY_MS = 800; // Safe balanced speed
const sleep = ms => new Promise(r => setTimeout(r, ms));
let userCache = {};
let historyCache = {}; // Added this to prevent the ReferenceError
function getDomain(db) {
const mapping = {
'commonswiki': 'commons.wikimedia.org', 'metawiki': 'meta.wikimedia.org', 'wikidatawiki': 'www.wikidata.org',
'mediawikiwiki': 'www.mediawiki.org', 'sourceswiki': 'wikisource.org', 'foundationwiki': 'foundation.wikimedia.org',
'incubatorwiki': 'incubator.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org',
'be_x_oldwiki': 'be-tarask.wikipedia.org', 'labswiki': 'wikitech.wikimedia.org',
'wikimaniawiki': 'wikimania.wikimedia.org', 'specieswiki': 'species.wikimedia.org',
'abstractwiki': 'abstract.wikipedia.org', 'betawikiversity': 'beta.wikiversity.org',
'mo_wiki': 'ro.wikipedia.org',
'testwiki': 'test.wikipedia.org', 'test2wiki': 'test2.wikipedia.org',
'wikifunctionswiki': 'www.wikifunctions.org',
'testcommonswiki': 'test-commons.wikimedia.org',
'testwikidatawiki': 'test.wikidata.org',
};
if (mapping[db]) return mapping[db];
const name = db.replace(/_/g, '-');
if (name.endsWith('wikisource')) return name.slice(0, -10) + '.wikisource.org';
if (name.endsWith('wikiversity')) return name.slice(0, -11) + '.wikiversity.org';
if (name.endsWith('wiktionary')) return name.slice(0, -10) + '.wiktionary.org';
if (name.endsWith('wikivoyage')) return name.slice(0, -10) + '.wikivoyage.org';
if (name.endsWith('wikibooks')) return name.slice(0, -9) + '.wikibooks.org';
if (name.endsWith('wikiquote')) return name.slice(0, -9) + '.wikiquote.org';
if (name.endsWith('wikinews')) return name.slice(0, -8) + '.wikinews.org';
if (name.endsWith('wikimedia')) return name.slice(0, -9) + '.wikimedia.org';
if (name.endsWith('wiki')) return name.slice(0, -4) + '.wikipedia.org';
return name + '.wikipedia.org';
}
mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi']).then(function() {
const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { anonymous: true });
const now = new Date();
let results = {}, emptyWikis = [], failedWikis = [], isRunning = false;
function setupUI() {
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
// CHANGE: Dynamically generate dropdown options for years and months
let yearOpts = '';
for (let y = currentYear; y >= 2005; y--) {
yearOpts += `<option value="${y}">${y}</option>`;
}
let monthOptsFrom = '';
let monthOptsTo = '';
for (let m = 1; m <= 12; m++) {
monthOptsFrom += `<option value="${m}" ${m === 1 ? 'selected' : ''}>${m}</option>`;
monthOptsTo += `<option value="${m}" ${m === currentMonth ? 'selected' : ''}>${m}</option>`;
}
$('#firstHeading').text('GlobalCheckUserStats.js');
$('#mw-content-text').empty().append(`
<div style="border:1px solid #a2a9b1; padding:15px; background:#f8f9fa;">
<h3>Audit Range</h3>
From:
<select id="y-f" style="width:70px;">${yearOpts}</select>
<select id="m-f" style="width:50px;">${monthOptsFrom}</select>
To:
<select id="y-t" style="width:70px;">${yearOpts}</select>
<select id="m-t" style="width:50px;">${monthOptsTo}</select>
<button id="start" class="mw-ui-button mw-ui-progressive" style="margin-left:15px;">Run GlobalCheckUserStats.js</button>
<button id="stop" class="mw-ui-button mw-ui-destructive" disabled>Stop</button>
<div id="status-msg" style="margin-top:10px; font-weight:bold; color:#0056b3;">Ready.</div>
<div style="margin-top:5px;"><progress id="bar" value="0" max="${rawWikis.length}" style="width:100%"></progress></div>
<textarea id="out" style="width:100%; height:450px; margin-top:10px; display:none; font-family:monospace; font-size:12px;"></textarea>
</div>
`);
$('#start').click(() => runAudit($('#y-f').val(), $('#m-f').val(), $('#y-t').val(), $('#m-t').val()));
$('#stop').click(() => isRunning = false);
}
// NEW: Fetch monthly averaged editors and total edits from Wikimedia Analytics API
async function fetchWikiMetrics(db, start, end) {
const project = getDomain(db);
const aqsStart = start.split('T')[0].replace(/-/g, '');
const aqsEnd = end.split('T')[0].replace(/-/g, '');
const editorsUrl = `https://wikimedia.org/api/rest_v1/metrics/editors/aggregate/${project}/all-editor-types/all-page-types/all-activity-levels/monthly/${aqsStart}/${aqsEnd}`;
const editsUrl = `https://wikimedia.org/api/rest_v1/metrics/edits/aggregate/${project}/all-editor-types/all-page-types/monthly/${aqsStart}/${aqsEnd}`;
const fetchWithRetry = async (url) => {
let attempts = 0;
while (attempts < 5) {
try {
const res = await fetch(url);
if (res.status === 429) {
let wait = (attempts + 1) * 7000;
$('#status-msg').text(`AQS limit! Cooling down ${wait/1000}s...`);
await sleep(wait);
attempts++;
continue;
}
if (!res.ok) return null;
return await res.json();
} catch (e) {
attempts++;
await sleep(2000);
}
}
return null;
};
try {
const [edRes, etRes] = await Promise.all([
fetchWithRetry(editorsUrl),
fetchWithRetry(editsUrl)
]);
if (!edRes?.items?.[0]?.results || !etRes?.items?.[0]?.results) {
return { avgEditors: 0, totalEdits: 0 };
}
const editorsResults = edRes?.items?.[0]?.results || [];
const avgEditors = editorsResults.length > 0
? editorsResults.reduce((sum, m) => sum + m.editors, 0) / editorsResults.length
: 0;
const totalEdits = etRes?.items?.[0]?.results
? etRes.items[0].results.reduce((sum, m) => sum + m.edits, 0)
: 0;
return { avgEditors, totalEdits };
} catch (e) {
return { avgEditors: 0, totalEdits: 0 };
}
}
// Helper function to check global rights history on Meta-Wiki
async function checkGlobalHistory(user, start, end) {
try {
const res = await metaApi.get({
action: 'query',
list: 'logevents',
letype: 'gblrights',
letitle: 'User:' + user,
formatversion: 2
});
const logs = res.query.logevents || [];
const auditStart = new Date(start);
const auditEnd = new Date(end);
let wasSteward = false, wasStaff = false, wasOmbuds = false;
for (const e of logs) {
const ts = new Date(e.timestamp);
const p = e.params || {};
const added = p.newGroups || p[1] || [];
const removed = p.oldGroups || p[0] || [];
// If a removal happened AFTER the audit started, the user held the rights during the period
if (ts > auditStart) {
if (removed.includes('steward')) wasSteward = true;
if (removed.includes('staff')) wasStaff = true;
if (removed.includes('ombuds')) wasOmbuds = true;
}
// If an addition happened BEFORE the audit ended, the user held the rights during the period
if (ts < auditEnd) {
if (added.includes('steward')) wasSteward = true;
if (added.includes('staff')) wasStaff = true;
if (added.includes('ombuds')) wasOmbuds = true;
}
}
return { wasSteward, wasStaff, wasOmbuds };
} catch (e) {
// Fallback for API errors
return { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
async function fetchUserData(user, db, start, end) {
// Priority 0: Use cache for current global groups
if (!userCache[user]) {
try {
const gres = await metaApi.get({ action: 'query', meta: 'globaluserinfo', guiprop: 'groups', guiuser: user, formatversion: 2 });
userCache[user] = gres.query.globaluserinfo.groups || [];
} catch(e) { userCache[user] = []; }
}
const gGroups = userCache[user];
const isGloballyStaff = gGroups.includes('staff');
const isGloballySteward = gGroups.includes('steward');
const isGloballyOmbuds = gGroups.includes('ombuds');
// Check current local CheckUser rights
let isCurrentLocal = false;
try {
const localApi = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
const ures = await localApi.get({ action: 'query', list: 'users', ususers: user, usprop: 'groups', formatversion: 2 });
const lGroups = (ures.query.users[0] && ures.query.users[0].groups) || [];
isCurrentLocal = lGroups.includes('checkuser');
} catch(e) { /* API failure fallback */ }
// Rights log analysis for the specific Wiki
const target = 'User:' + user + '@' + db;
let logText = ''; // Default is empty, filled only if there are logs
let addTime = null, removeTime = null, isSelfAssign = false;
// CHANGE: Variables to track the longest duration and the number of actions
let maxDurationMins = -1;
let longestTimeStr = "";
let assignCount = 0;
try {
const res = await metaApi.get({
action: 'query', list: 'logevents', letype: 'rights', letitle: target,
lestart: end, leend: start, ledir: 'older',
lelimit: 'max', // ADDED: Loads up to 500 logs instead of 10
formatversion: 2
});
const events = res.query.logevents || [];
let filtered = [];
// Variables to pair removal and addition (iterating newest to oldest)
let tempRemoveTime = null;
let tempRemoveTimeStr = "Not removed";
events.forEach(e => {
const p = e.params || {};
const cuAdded = (p.add || []).includes('checkuser') || ((p.newgroups || []).includes('checkuser') && !(p.oldgroups || []).includes('checkuser'));
const cuRemoved = (p.remove || []).includes('checkuser') || (!(p.newgroups || []).includes('checkuser') && (p.oldgroups || []).includes('checkuser'));
const exactTimeLog = e.timestamp.replace('T', ' ').replace('Z', '');
if (cuRemoved) {
tempRemoveTime = new Date(e.timestamp);
tempRemoveTimeStr = exactTimeLog;
// Save the most recent removal for the overall table role categorization
if (!removeTime) {
removeTime = tempRemoveTime;
if (e.user === user || !e.user) isSelfAssign = true;
}
}
if (cuAdded) {
const currentAddTime = new Date(e.timestamp);
let metadataRemoveTime = tempRemoveTime;
let metadataRemoveTimeStr = tempRemoveTimeStr;
// Save the most recent addition for the overall table role categorization
if (!addTime) {
addTime = currentAddTime;
isSelfAssign = (e.user === user);
}
// Fallback: Check expiry in metadata if explicit removal log is missing
const metadata = p.newmetadata || [];
const cuMeta = metadata.find(m => m.group === 'checkuser');
if (cuMeta && cuMeta.expiry && cuMeta.expiry !== 'infinity' && tempRemoveTimeStr === "Not removed") {
metadataRemoveTime = new Date(cuMeta.expiry);
metadataRemoveTimeStr = cuMeta.expiry.replace('T', ' ').replace('Z', '');
if (!removeTime) removeTime = metadataRemoveTime;
}
// Calculate duration for this specific 1-line pair
let pairDuration = "Unknown";
if (metadataRemoveTime) {
const diffMs = Math.abs(metadataRemoveTime - currentAddTime);
const diffMins = Math.round(diffMs / 60000); // CHANGE: Rounds to the nearest whole minute
const days = Math.floor(diffMins / 1440);
const hours = Math.floor((diffMins % 1440) / 60);
const mins = diffMins % 60;
if (days > 0) pairDuration = `${days}d ${hours}h ${mins}m`;
else if (hours > 0) pairDuration = `${hours}h ${mins}m`;
else if (mins > 0) pairDuration = `${mins}m`;
else pairDuration = "<1m";
// CHANGE: Track the total number of assignments and store the absolute longest duration
assignCount++;
if (diffMins > maxDurationMins) {
maxDurationMins = diffMins;
longestTimeStr = pairDuration;
}
}
// Push the combined 1-line format to the log using commas
filtered.push(`* Added: ${exactTimeLog}, Removed: ${metadataRemoveTimeStr}, Duration: ${pairDuration}`);
// Reset pair variables for the next older event in the loop
tempRemoveTime = null;
tempRemoveTimeStr = "Not removed";
}
});
// Edge case: If a removal was found but the addition happened before the audit period
if (tempRemoveTime && tempRemoveTimeStr !== "Not removed") {
filtered.push(`* Added: Before audit period, Removed: ${tempRemoveTimeStr}, Duration: Unknown`);
}
// Joins logs with newlines for the report
if (filtered.length) {
logText = '\n' + filtered.join('\n');
}
} catch (err) { /* Rights log query failed */ }
// --- GLOBAL HISTORY CHECK (WITH CACHE) ---
if (!historyCache[user]) {
if (!isGloballySteward || !isGloballyStaff || !isGloballyOmbuds) {
// Check Meta-Wiki history logs only if user is missing at least one global role today
historyCache[user] = await checkGlobalHistory(user, start, end);
} else {
historyCache[user] = { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
const history = historyCache[user];
// --- HIERARCHY LOGIC (CheckUser Audit Focus) ---
let roleLabel = "";
// 1. STAFF (Highest audit priority for WMF accountability)
if (isGloballyStaff || history.wasStaff) {
roleLabel = isGloballyStaff ? "Current Staff" : "Former Staff (in period)";
}
// 2. LOCAL CHECKUSER (Overrides Steward/Ombudsman roles if local rights exist)
else if (isCurrentLocal) {
roleLabel = "Current Local CheckUser";
}
// 3. STEWARD - SPECIFIC INTERVENTION (Using exact self-assignment duration or max duration if multiple)
else if (longestTimeStr && isSelfAssign) {
if (assignCount > 1) {
roleLabel = `Steward action (Self-assign: longest ${longestTimeStr}, see log)`;
} else {
roleLabel = `Steward action (Self-assign: ${longestTimeStr})`;
}
}
// 4. STEWARD - GENERAL (Global rights usage on external wikis)
else if (isGloballySteward || history.wasSteward) {
roleLabel = isGloballySteward ? "Current Steward" : "Former Steward (in period)";
}
// 5. OMBUDSMAN (Global oversight role)
else if (isGloballyOmbuds || history.wasOmbuds) {
roleLabel = isGloballyOmbuds ? "Current Ombudsman" : "Former Ombudsman (in period)";
}
// 6. FORMER LOCAL CheckUser (Fallback for those who lost rights but performed actions)
else {
roleLabel = "Former Local CheckUser";
}
return { role: roleLabel, log: logText };
}
async function runAudit(yf, mf, yt, mt) {
isRunning = true;
results = {};
emptyWikis = [];
failedWikis = [];
userCache = {};
historyCache = {};
$('#start').prop('disabled', true);
$('#stop').prop('disabled', false);
$('#out').hide();
// Format ISO timestamps for the API
const START = `${yf}-${mf.toString().padStart(2, '0')}-01T00:00:00Z`;
const lastDay = new Date(Date.UTC(yt, mt, 0)).getUTCDate();
const END = `${yt}-${mt.toString().padStart(2, '0')}-${lastDay.toString().padStart(2, '0')}T23:59:59Z`;
// Generate list of months for dynamic table columns
const monthCols = [];
let currY = parseInt(yf), currM = parseInt(mf);
const endY = parseInt(yt), endM = parseInt(mt);
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
while (currY < endY || (currY === endY && currM <= endM)) {
monthCols.push({
key: `${currY}-${String(currM).padStart(2, '0')}`,
label: `${monthNames[currM - 1]} ${currY}` // e.g., "Jan 2024"
});
currM++;
if (currM > 12) { currM = 1; currY++; }
}
for (let i = 0; i < rawWikis.length && isRunning; i++) {
const db = rawWikis[i];
$('#status-msg').text(`Scanning ${db} (${i + 1}/${rawWikis.length})...`);
let successLocal = false;
let continueToken = null; // Token for pagination
while (!successLocal && isRunning) {
try {
const api = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
let params = {
action: 'query',
list: 'checkuserlog',
culfrom: START,
culto: END,
culdir: 'newer',
cullimit: 'max',
formatversion: 2
};
// Add continue token if we are on page 2 or further
if (continueToken) {
Object.assign(params, continueToken);
}
let res = await api.get(params);
const ent = (res.query && res.query.checkuserlog && res.query.checkuserlog.entries) || [];
if (ent.length) {
if (!results[db]) results[db] = {};
// Sum up actions per user (total and per month)
ent.forEach(e => {
const u = e.checkuser;
if (!results[db][u]) results[db][u] = { total: 0, months: {} };
const date = new Date(e.timestamp);
const mKey = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
results[db][u].total++;
results[db][u].months[mKey] = (results[db][u].months[mKey] || 0) + 1;
});
}
// Pagination check
if (res.continue && isRunning) {
continueToken = res.continue;
$('#status-msg').text(`Scanning ${db} (fetching next page)...`);
} else {
// Wiki scan finished (either no entries at all or all pages fetched)
if (!results[db]) emptyWikis.push(db);
successLocal = true;
}
} catch (err) {
const status = (err && err.xhr) ? err.xhr.status : (err && err.status);
if (status === 429) {
// Rate limit hit, wait 30s and retry the SAME wiki/page
$('#status-msg').text(`Rate limit on ${db}. Waiting 30s...`);
await sleep(30000);
} else {
// Critical error (CORS, DNS, etc.), skip this wiki
console.error(`Skipping ${db} due to error:`, err);
failedWikis.push(db);
successLocal = true;
}
}
}
$('#bar').val(i + 1);
await sleep(DELAY_MS);
}
// --- REPORT GENERATION ---
$('#status-msg').text(isRunning ? `Generating report...` : `Generating partial report...`);
const now = new Date();
const timestamp = now.getUTCFullYear() + '-' +
String(now.getUTCMonth() + 1).padStart(2, '0') + '-' +
String(now.getUTCDate()).padStart(2, '0') + ' ' +
String(now.getUTCHours()).padStart(2, '0') + ':' +
String(now.getUTCMinutes()).padStart(2, '0') + ' (UTC)';
let wt = `== Global CheckUser Stats (${mf}/${yf} - ${mt}/${yt}) ==\n`;
wt += `''Report generated on: ${timestamp}''\n\n`;
if (!isRunning) wt += `'''Note: This is a partial report (stopped manually).'''\n\n`;
let rightsLog = `\n== Rights Log (In Period) ==\n`;
// UPDATE HEADER
let headerMonths = monthCols.map(m => `!! ${m.label}`).join(' ');
wt += `{| class="wikitable sortable" style="font-size:90%; text-align:right;"\n`;
wt += `! Wiki / User !! Category !! '''Total''' !! per 1k users !! per 1k edits ${headerMonths}\n`;
const sortedDBs = Object.keys(results).sort();
for (const db of sortedDBs) {
if (!isRunning) break;
await sleep(1500);
$('#status-msg').text(`Fetching metrics for ${db}...`);
const metrics = await fetchWikiMetrics(db, START, END);
// Wiki Header Row
wt += `|-\n! colspan="${5 + monthCols.length}" style="background:#eaecf0; text-align:left;" | [[w:project:${db}|${db}]]\n`;
let wikiTotalActions = 0;
let wikiMonthlySums = {};
monthCols.forEach(col => wikiMonthlySums[col.key] = 0);
const sortedUsers = Object.keys(results[db]).sort();
for (const user of sortedUsers) {
const m = await fetchUserData(user, db, START, END);
const userData = results[db][user];
wikiTotalActions += userData.total;
let per1kUsers = metrics.avgEditors > 0 ? ((userData.total / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let per1kEdits = metrics.totalEdits > 0 ? ((userData.total / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
let rowStr = `|-\n| style="text-align:left;" | [[User:${user}|${user}]] || <small>${m.role}</small> || '''${userData.total}''' || ${per1kUsers} || ${per1kEdits}`;
monthCols.forEach(col => {
const val = userData.months[col.key] || 0;
rowStr += ` || ${val}`;
wikiMonthlySums[col.key] += val;
});
wt += `${rowStr}\n`;
if (m.log) {
rightsLog += `'''${user}@${db}''':${m.log}\n\n`;
}
}
// WIKI TOTAL ROW
let wikiPer1kUsers = metrics.avgEditors > 0 ? ((wikiTotalActions / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let wikiPer1kEdits = metrics.totalEdits > 0 ? ((wikiTotalActions / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
let totalRow = `|-\n| style="text-align:left; background:#f8f9fa; font-weight:bold;" | TOTAL ${db} || style="background:#f8f9fa;" | — || style="background:#f8f9fa; font-weight:bold;" | ${wikiTotalActions} || style="background:#f8f9fa; font-weight:bold;" | ${wikiPer1kUsers} || style="background:#f8f9fa; font-weight:bold;" | ${wikiPer1kEdits}`;
monthCols.forEach(col => {
totalRow += ` || style="background:#f8f9fa; font-weight:bold;" | ${wikiMonthlySums[col.key]}`;
});
wt += `${totalRow}\n`;
}
wt += `|}\n\n== Projects with 0 actions ==\n<div style="font-size:85%; color:#54595d;">${emptyWikis.sort().join(', ')}</div>\n`;
if (failedWikis.length) {
wt += `\n== Errors (Failed to scan) ==\n${failedWikis.sort().join(', ')}\n`;
}
wt += rightsLog;
wt += `\n== References ==\n<references />\n`;
$('#out').val(wt).show();
$('#status-msg').text(isRunning ? `Audit complete!` : `Audit stopped. Partial report ready.`);
$('#start').prop('disabled', false);
$('#stop').prop('disabled', true);
}
setupUI();
});
})();
fxz7pnveq2f182q5vg9vs10fxb8g4l1
737245
737210
2026-04-08T18:54:13Z
MrJaroslavik
44012
e
737245
javascript
text/javascript
// GlobalCheckUserStats.js
// Features:
// - Global Audit: Scans CheckUser logs across 900+ Wikimedia projects at once.
// - Smart Categorization: Identifies roles (Local CU, Steward, Staff, etc.).
// - Steward Logic: Detects temporary access, exact durations, and longest active periods.
// - Deep Scan: Automated pagination to bypass the 500-entry API limit (essential for enwiki).
// - Robust Connection: Automated retries for 429 rate-limits and custom domain mapping.
// - Full Reporting: Outputs sortable Wikitables and detailed rights change logs.
// - Custom UI: Integrated control panel on [[Special:BlankPage/GlobalCheckUserStats]].
// - With help of Gemini 3
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').indexOf('GlobalCheckUserStats') === -1) return;
const rawWikis = ['abstractwiki','abwiki','acewiki','adywiki','afwiki','afwikibooks','afwikiquote','afwiktionary','alswiki','altwiki','amiwiki','amwiki','amwiktionary','angwiki','angwiktionary','annwiki','anpwiki','anwiki','anwiktionary','arcwiki','arwiki','arwikibooks','arwikimedia','arwikinews','arwikiquote','arwikisource','arwikiversity','arwiktionary','arywiki','arzwiki','astwiki','astwiktionary','aswiki','aswikiquote','aswikisource','atjwiki','avkwiki','avwiki','awawiki','aywiki','aywiktionary','azbwiki','azwiki','azwikibooks','azwikiquote','azwikisource','azwiktionary','banwiki','banwikisource','barwiki','bat_smgwiki','bawiki','bawikibooks','bbcwiki','bclwiki','bclwikiquote','bclwikisource','bclwiktionary','bdrwiki','bdwikimedia','be_x_oldwiki','betawikiversity','bewiki','bewikibooks','bewikimedia','bewikiquote','bewikisource','bewiktionary','bewwiki','bewwiktionary','bgwiki','bgwikibooks','bgwikiquote','bgwikisource','bgwiktionary','bhwiki','biwiki','bjnwiki','bjnwikiquote','bjnwiktionary','blkwiki','blkwiktionary','bmwiki','bnwiki','bnwikibooks','bnwikiquote','bnwikisource','bnwikivoyage','bnwiktionary','bowiki','bpywiki','brwiki','brwikimedia','brwikiquote','brwikisource','brwiktionary','bswiki','bswikibooks','bswikinews','bswikiquote','bswikisource','bswiktionary','btmwiki','btmwiktionary','bugwiki','bxrwiki','cawiki','cawikibooks','cawikimedia','cawikinews','cawikiquote','cawikisource','cawiktionary','cbk_zamwiki','cdowiki','cebwiki','cewiki','chrwiki','chrwiktionary','chwiki','chywiki','ckbwiki','ckbwiktionary','commonswiki','cowiki','cowikimedia','cowiktionary','crhwiki','csbwiki','csbwiktionary','cswiki','cswikibooks','cswikinews','cswikiquote','cswikisource','cswikiversity','cswikivoyage','cswiktionary','cuwiki','cvwiki','cvwikibooks','cywiki','cywikibooks','cywikiquote','cywikisource','cywiktionary','dagwiki','dawiki','dawikibooks','dawikiquote','dawikisource','dawiktionary','dewiki','dewikibooks','dewikinews','dewikiquote','dewikisource','dewikiversity','dewikivoyage','dewiktionary','dgawiki','dinwiki','diqwiki','diqwiktionary','dkwikimedia','dsbwiki','dtpwiki','dtywiki','dvwiki','dvwiktionary','dzwiki','eewiki','elwiki','elwikibooks','elwikinews','elwikiquote','elwikisource','elwikiversity','elwikivoyage','elwiktionary','emlwiki','enwiki','enwikibooks','enwikinews','enwikiquote','enwikisource','enwikiversity','enwikivoyage','enwiktionary','eowiki','eowikibooks','eowikinews','eowikiquote','eowikisource','eowikivoyage','eowiktionary','eswiki','eswikibooks','eswikinews','eswikiquote','eswikisource','eswikiversity','eswikivoyage','eswiktionary','etwiki','etwikibooks','eewikimedia','etwikiquote','etwikisource','etwiktionary','euwiki','euwikibooks','euwikiquote','euwikisource','euwiktionary','extwiki','fatwiki','fawiki','fawikibooks','fawikinews','fawikiquote','fawikisource','fawikivoyage','fawiktionary','ffwiki','fiu_vrowiki','fiwiki','fiwikibooks','fiwikimedia','fiwikinews','fiwikiquote','fiwikisource','fiwikiversity','fiwikivoyage','fiwiktionary','fjwiki','fjwiktionary','fonwiki','foundationwiki','fowiki','fowikisource','fowiktionary','frpwiki','frrwiki','frwiki','frwikibooks','frwikinews','frwikiquote','frwikisource','frwikiversity','frwikivoyage','frwiktionary','furwiki','fywiki','fywikibooks','fywiktionary','gagwiki','ganwiki','gawiki','gawiktionary','gcrwiki','gdwiki','gdwiktionary','glkwiki','glwiki','glwikibooks','glwikiquote','glwikisource','glwiktionary','gnwiki','gnwiktionary','gomwiki','gomwiktionary','gorwiki','gorwikiquote','gorwiktionary','gotwiki','gpewiki','gucwiki','gurwiki','guwiki','guwikiquote','guwikisource','guwiktionary','guwwiki','guwwikinews','guwwikiquote','guwwiktionary','gvwiki','gvwiktionary','hakwiki','hawiki','hawiktionary','hawwiki','hewiki','hewikibooks','hewikinews','hewikiquote','hewikisource','hewikivoyage','hewiktionary','hifwiki','hifwiktionary','hiwiki','hiwikibooks','hiwikiquote','hiwikisource','hiwikiversity','hiwikivoyage','hiwiktionary','hrwiki','hrwikibooks','hrwikiquote','hrwikisource','hrwiktionary','hsbwiki','hsbwiktionary','htwiki','huwiki','huwikibooks','huwikisource','huwiktionary','hywiki','hywikibooks','hywikiquote','hywikisource','hywiktionary','hywwiki','iawiki','iawikibooks','iawiktionary','ibawiki','idwiki','idwikibooks','idwikiquote','idwikisource','idwikivoyage','idwiktionary','iewiki','iewiktionary','iglwiki','igwiki','igwikiquote','igwiktionary','ikwiki','ilowiki','incubatorwiki','inhwiki','iowiki','iowiktionary','iswiki','iswikibooks','iswikiquote','iswikisource','iswiktionary','itwiki','itwikibooks','itwikinews','itwikiquote','itwikisource','itwikiversity','itwikivoyage','itwiktionary','iuwiki','iuwiktionary','jamwiki','jawiki','jawikibooks','jawikinews','jawikisource','jawikiversity','jawikivoyage','jawiktionary','jbowiki','jbowiktionary','jvwiki','jvwikisource','jvwiktionary','kaawiki','kaawiktionary','kabwiki','kaiwiki','kajwiki','kawiki','kawikibooks','kawikiquote','kawikisource','kawiktionary','kbdwiki','kbdwiktionary','kbpwiki','kcgwiki','kcgwiktionary','kgewiki','kgwiki','kiwiki','kkwiki','kkwikibooks','kkwiktionary','klwiktionary','kmwiki','kmwikibooks','kmwiktionary','kncwiki','knwiki','knwikiquote','knwikisource','knwiktionary','koiwiki','kowiki','kowikibooks','kowikinews','kowikiquote','kowikisource','kowikiversity','kowiktionary','krcwiki','kshwiki','kswiki','kswiktionary','kuswiki','kuwiki','kuwikibooks','kuwikiquote','kuwiktionary','kvwiki','kwwiki','kwwiktionary','kywiki','kywikiquote','kywiktionary','labswiki','ladwiki','lawiki','lawikibooks','lawikiquote','lawikisource','lawiktionary','lbewiki','lbwiki','lbwiktionary','lezwiki','lfnwiki','lgwiki','lijwiki','lijwikisource','liwiki','liwikibooks','liwikinews','liwikiquote','liwikisource','liwiktionary','lldwiki','lmowiki','lmowiktionary','lnwiki','lnwiktionary','lowiki','lowiktionary','ltgwiki','ltwiki','ltwikibooks','ltwikiquote','ltwikisource','ltwiktionary','lvwiki','lvwiktionary','madwiki','madwikisource','madwiktionary','maiwiki','map_bmswiki','mdfwiki','mediawikiwiki','metawiki','mgwiki','mgwikibooks','mgwiktionary','mhrwiki','minwiki','minwikibooks','minwikisource','minwiktionary','miwiki','miwiktionary','mkwiki','mkwikibooks','mkwikimedia','mkwikisource','mkwiktionary','mlwiki','mlwikibooks','mlwikiquote','mlwikisource','mlwiktionary','mniwiki','mniwiktionary','mnwiki','mnwiktionary','mnwwiki','mnwwiktionary','moswiki','mrjwiki','mrwiki','mrwikibooks','mrwikiquote','mrwikisource','mrwiktionary','mswiki','mswikibooks','mswikiquote','mswikisource','mswiktionary','mtwiki','mtwiktionary','mwlwiki','mxwikimedia','myvwiki','mywiki','mywikisource','mywiktionary','mznwiki','nahwiki','nahwiktionary','napwiki','napwikisource','nawiktionary','nds_nlwiki','ndswiki','ndswiktionary','newiki','newikibooks','newiktionary','newwiki','niawiki','niawiktionary','nlwiki','nlwikibooks','nlwikimedia','nlwikinews','nlwikiquote','nlwikisource','nlwikivoyage','nlwiktionary','nnwiki','nnwikiquote','nnwiktionary','novwiki','nowiki','nowikibooks','nowikimedia','nowikinews','nowikiquote','nowikisource','nowiktionary','nqowiki','nrmwiki','nrwiki','nsowiki','nupwiki','nvwiki','nycwikimedia','nywiki','ocwiki','ocwikibooks','ocwiktionary','olowiki','omwiki','omwiktionary','orwiki','orwikisource','orwiktionary','oswiki','outreachwiki','pagwiki','pamwiki','papwiki','pawiki','pawikibooks','pawikisource','pawiktionary','pcdwiki','pcmwiki','pcmwikiquote','pdcwiki','pflwiki','piwiki','plwiki','plwikibooks','plwikimedia','plwikinews','plwikiquote','plwikisource','plwikivoyage','plwiktionary','pmswiki','pmswikisource','pnbwiki','pnbwiktionary','pntwiki','pplwiki','pswiki','pswikivoyage','pswiktionary','ptwiki','ptwikibooks','ptwikimedia','ptwikinews','ptwikiquote','ptwikisource','ptwikiversity','ptwikivoyage','ptwiktionary','pwnwiki','quwiktionary','rkiwiki','rmwiki','rmywiki','rnwiki','roa_rupwiki','roa_rupwiktionary','roa_tarawiki','rowiki','rowikibooks','rowikinews','rowikiquote','rowiktionary','rskwiki','ruewiki','ruwiki','ruwikibooks','ruwikinews','ruwikiquote','ruwikisource','ruwikiversity','ruwikivoyage','ruwiktionary','rwwiki','rwwiktionary','sahwiki','sahwikiquote','sahwikisource','satwiki','satwiktionary','sawiki','sawikibooks','sawikiquote','sawikisource','sawiktionary','scnwiki','scnwiktionary','scowiki','scwiki','sdwiki','sdwiktionary','sewiki','sewikimedia','sgwiki','sgwiktionary','shiwiki','shnwiki','shnwikibooks','shnwikinews','shnwikivoyage','shnwiktionary','shwiki','shwiktionary','shywiktionary','simplewiki','simplewiktionary','siwiki','siwikibooks','siwiktionary','skwiki','skrwiki','skrwiktionary','slwiki','slwikibooks','slwikiquote','slwikisource','slwikiversity','slwiktionary','smnwiki','smwiki','smwiktionary','snwiki','sourceswiki','sowiki','sowiktionary','specieswiki','sqwiki','sqwikibooks','sqwikinews','sqwikiquote','sqwiktionary','srnwiki','srwiki','srwikibooks','srwikinews','srwikiquote','srwikisource','srwiktionary','sswiki','sswiktionary','stqwiki','stwiki','stwiktionary','suwiki','suwikiquote','suwikisource','suwiktionary','svwiki','svwikibooks','svwikinews','svwikiquote','svwikisource','svwikiversity','svwikivoyage','svwiktionary','swwiki','swwiktionary','sylwiki','szlwiki','szywiki','tawiki','tawikibooks','tawikinews','tawikiquote','tawikisource','tawiktionary','taywiki','tcywiki','tcywikisource','tcywiktionary','tddwiki','tewiki','tewikibooks','tewikiquote','tewikisource','tewiktionary','tgwiki','tgwikibooks','tgwiktionary','thwiki','thwikibooks','thwikimedia','thwikiquote','thwikisource','thwiktionary','tigwiki','tiwiki','tiwiktionary','tkwiki','tkwiktionary','tlwiki','tlwikibooks','tlwikiquote','tlwikisource','tlwiktionary','tlywiki','tnwiki','tnwiktionary','tokwiki','towiki','tpiwiki','tpiwiktionary','trvwiki','trwiki','trwikibooks','trwikimedia','trwikinews','trwikiquote','trwikisource','trwikivoyage','trwiktionary','tswiki','tswiktionary','ttwiki','ttwikibooks','ttwikiquote','ttwiktionary','tumwiki','twwiki','twwiktionary','tyvwiki','tywiki','uawikimedia','udmwiki','ugwiki','ugwiktionary','ukwiki','ukwikibooks','ukwikinews','ukwikiquote','ukwikisource','ukwikivoyage','ukwiktionary','urwiki','urwikibooks','urwikiquote','urwikisource','urwiktionary','uzwiki','uzwikiquote','uzwiktionary','vecwiki','vecwikisource','vecwiktionary','vepwiki','vewiki','viwiki','viwikibooks','viwikiquote','viwikisource','viwikivoyage','viwiktionary','vlswiki','vowiki','vowiktionary','warwiki','wawiki','wawikisource','wawiktionary','wikidatawiki','wikimaniawiki','wowiki','wowiktionary','wuuwiki','xalwiki','xhwiki','xmfwiki','yiwiki','yiwikisource','yiwiktionary','yowiki','zawiki','zeawiki','zghwiki','zghwiktionary','zh_classicalwiki','zh_min_nanwiki','zh_min_nanwikisource','zh_min_nanwiktionary','zh_yuewiki','zhwiki','zhwikibooks','zhwikinews','zhwikiquote','zhwikisource','zhwikiversity','zhwikivoyage','zhwiktionary','zuwiki','zuwiktionary','test2wiki','testwiki'];
const DELAY_MS = 800; // Safe balanced speed
const sleep = ms => new Promise(r => setTimeout(r, ms));
let userCache = {};
let historyCache = {}; // Added this to prevent the ReferenceError
function getDomain(db) {
const mapping = {
'commonswiki': 'commons.wikimedia.org', 'metawiki': 'meta.wikimedia.org', 'wikidatawiki': 'www.wikidata.org',
'mediawikiwiki': 'www.mediawiki.org', 'sourceswiki': 'wikisource.org', 'foundationwiki': 'foundation.wikimedia.org',
'incubatorwiki': 'incubator.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org',
'be_x_oldwiki': 'be-tarask.wikipedia.org', 'labswiki': 'wikitech.wikimedia.org',
'wikimaniawiki': 'wikimania.wikimedia.org', 'specieswiki': 'species.wikimedia.org',
'abstractwiki': 'abstract.wikipedia.org', 'betawikiversity': 'beta.wikiversity.org',
'mo_wiki': 'ro.wikipedia.org',
'testwiki': 'test.wikipedia.org', 'test2wiki': 'test2.wikipedia.org',
'wikifunctionswiki': 'www.wikifunctions.org',
'testcommonswiki': 'test-commons.wikimedia.org',
'testwikidatawiki': 'test.wikidata.org',
};
if (mapping[db]) return mapping[db];
const name = db.replace(/_/g, '-');
if (name.endsWith('wikisource')) return name.slice(0, -10) + '.wikisource.org';
if (name.endsWith('wikiversity')) return name.slice(0, -11) + '.wikiversity.org';
if (name.endsWith('wiktionary')) return name.slice(0, -10) + '.wiktionary.org';
if (name.endsWith('wikivoyage')) return name.slice(0, -10) + '.wikivoyage.org';
if (name.endsWith('wikibooks')) return name.slice(0, -9) + '.wikibooks.org';
if (name.endsWith('wikiquote')) return name.slice(0, -9) + '.wikiquote.org';
if (name.endsWith('wikinews')) return name.slice(0, -8) + '.wikinews.org';
if (name.endsWith('wikimedia')) return name.slice(0, -9) + '.wikimedia.org';
if (name.endsWith('wiki')) return name.slice(0, -4) + '.wikipedia.org';
return name + '.wikipedia.org';
}
mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi']).then(function() {
const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { anonymous: true });
const now = new Date();
let results = {}, emptyWikis = [], failedWikis = [], isRunning = false;
function setupUI() {
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
// CHANGE: Dynamically generate dropdown options for years and months
let yearOpts = '';
for (let y = currentYear; y >= 2005; y--) {
yearOpts += `<option value="${y}">${y}</option>`;
}
let monthOptsFrom = '';
let monthOptsTo = '';
for (let m = 1; m <= 12; m++) {
monthOptsFrom += `<option value="${m}" ${m === 1 ? 'selected' : ''}>${m}</option>`;
monthOptsTo += `<option value="${m}" ${m === currentMonth ? 'selected' : ''}>${m}</option>`;
}
$('#firstHeading').text('GlobalCheckUserStats.js');
$('#mw-content-text').empty().append(`
<div style="border:1px solid #a2a9b1; padding:15px; background:#f8f9fa;">
<h3>Audit Range</h3>
From:
<select id="y-f" style="width:70px;">${yearOpts}</select>
<select id="m-f" style="width:50px;">${monthOptsFrom}</select>
To:
<select id="y-t" style="width:70px;">${yearOpts}</select>
<select id="m-t" style="width:50px;">${monthOptsTo}</select>
<button id="start" class="mw-ui-button mw-ui-progressive" style="margin-left:15px;">Run GlobalCheckUserStats.js</button>
<button id="stop" class="mw-ui-button mw-ui-destructive" disabled>Stop</button>
<div id="status-msg" style="margin-top:10px; font-weight:bold; color:#0056b3;">Ready.</div>
<div style="margin-top:5px;"><progress id="bar" value="0" max="${rawWikis.length}" style="width:100%"></progress></div>
<textarea id="out" style="width:100%; height:450px; margin-top:10px; display:none; font-family:monospace; font-size:12px;"></textarea>
</div>
`);
$('#start').click(() => runAudit($('#y-f').val(), $('#m-f').val(), $('#y-t').val(), $('#m-t').val()));
$('#stop').click(() => isRunning = false);
}
// NEW: Fetch monthly averaged editors and total edits from Wikimedia Analytics API
async function fetchWikiMetrics(db, start, end) {
const project = getDomain(db);
const aqsStart = start.split('T')[0].replace(/-/g, '');
const aqsEnd = end.split('T')[0].replace(/-/g, '');
const editorsUrl = `https://wikimedia.org/api/rest_v1/metrics/editors/aggregate/${project}/all-editor-types/all-page-types/all-activity-levels/monthly/${aqsStart}/${aqsEnd}`;
const editsUrl = `https://wikimedia.org/api/rest_v1/metrics/edits/aggregate/${project}/all-editor-types/all-page-types/monthly/${aqsStart}/${aqsEnd}`;
const fetchWithRetry = async (url) => {
let attempts = 0;
while (attempts < 5) {
try {
const res = await fetch(url);
if (res.status === 429) {
let wait = (attempts + 1) * 7000;
$('#status-msg').text(`AQS limit! Cooling down ${wait/1000}s...`);
await sleep(wait);
attempts++;
continue;
}
if (!res.ok) return null;
return await res.json();
} catch (e) {
attempts++;
await sleep(2000);
}
}
return null;
};
try {
const [edRes, etRes] = await Promise.all([
fetchWithRetry(editorsUrl),
fetchWithRetry(editsUrl)
]);
if (!edRes?.items?.[0]?.results || !etRes?.items?.[0]?.results) {
return { avgEditors: 0, totalEdits: 0 };
}
const editorsResults = edRes?.items?.[0]?.results || [];
const avgEditors = editorsResults.length > 0
? editorsResults.reduce((sum, m) => sum + m.editors, 0) / editorsResults.length
: 0;
const totalEdits = etRes?.items?.[0]?.results
? etRes.items[0].results.reduce((sum, m) => sum + m.edits, 0)
: 0;
return { avgEditors, totalEdits };
} catch (e) {
return { avgEditors: 0, totalEdits: 0 };
}
}
// Helper function to check global rights history on Meta-Wiki
async function checkGlobalHistory(user, start, end) {
try {
const res = await metaApi.get({
action: 'query',
list: 'logevents',
letype: 'gblrights',
letitle: 'User:' + user,
formatversion: 2
});
const logs = res.query.logevents || [];
const auditStart = new Date(start);
const auditEnd = new Date(end);
let wasSteward = false, wasStaff = false, wasOmbuds = false;
for (const e of logs) {
const ts = new Date(e.timestamp);
const p = e.params || {};
const added = p.newGroups || p[1] || [];
const removed = p.oldGroups || p[0] || [];
// If a removal happened AFTER the audit started, the user held the rights during the period
if (ts > auditStart) {
if (removed.includes('steward')) wasSteward = true;
if (removed.includes('staff')) wasStaff = true;
if (removed.includes('ombuds')) wasOmbuds = true;
}
// If an addition happened BEFORE the audit ended, the user held the rights during the period
if (ts < auditEnd) {
if (added.includes('steward')) wasSteward = true;
if (added.includes('staff')) wasStaff = true;
if (added.includes('ombuds')) wasOmbuds = true;
}
}
return { wasSteward, wasStaff, wasOmbuds };
} catch (e) {
// Fallback for API errors
return { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
async function fetchUserData(user, db, start, end) {
// Priority 0: Use cache for current global groups
if (!userCache[user]) {
try {
const gres = await metaApi.get({ action: 'query', meta: 'globaluserinfo', guiprop: 'groups', guiuser: user, formatversion: 2 });
userCache[user] = gres.query.globaluserinfo.groups || [];
} catch(e) { userCache[user] = []; }
}
const gGroups = userCache[user];
const isGloballyStaff = gGroups.includes('staff');
const isGloballySteward = gGroups.includes('steward');
const isGloballyOmbuds = gGroups.includes('ombuds');
// Check current local CheckUser rights
let isCurrentLocal = false;
try {
const localApi = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
const ures = await localApi.get({ action: 'query', list: 'users', ususers: user, usprop: 'groups', formatversion: 2 });
const lGroups = (ures.query.users[0] && ures.query.users[0].groups) || [];
isCurrentLocal = lGroups.includes('checkuser');
} catch(e) { /* API failure fallback */ }
// Rights log analysis for the specific Wiki
const target = 'User:' + user + '@' + db;
let logText = ''; // Default is empty, filled only if there are logs
let addTime = null, removeTime = null, isSelfAssign = false;
// CHANGE: Variables to track the longest duration and the number of actions
let maxDurationMins = -1;
let longestTimeStr = "";
let assignCount = 0;
try {
const res = await metaApi.get({
action: 'query', list: 'logevents', letype: 'rights', letitle: target,
lestart: end, leend: start, ledir: 'older',
lelimit: 'max', // ADDED: Loads up to 500 logs instead of 10
formatversion: 2
});
const events = res.query.logevents || [];
let filtered = [];
// Variables to pair removal and addition (iterating newest to oldest)
let tempRemoveTime = null;
let tempRemoveTimeStr = "Not removed";
events.forEach(e => {
const p = e.params || {};
const cuAdded = (p.add || []).includes('checkuser') || ((p.newgroups || []).includes('checkuser') && !(p.oldgroups || []).includes('checkuser'));
const cuRemoved = (p.remove || []).includes('checkuser') || (!(p.newgroups || []).includes('checkuser') && (p.oldgroups || []).includes('checkuser'));
const exactTimeLog = e.timestamp.replace('T', ' ').replace('Z', '');
if (cuRemoved) {
tempRemoveTime = new Date(e.timestamp);
tempRemoveTimeStr = exactTimeLog;
// Save the most recent removal for the overall table role categorization
if (!removeTime) {
removeTime = tempRemoveTime;
if (e.user === user || !e.user) isSelfAssign = true;
}
}
if (cuAdded) {
const currentAddTime = new Date(e.timestamp);
let metadataRemoveTime = tempRemoveTime;
let metadataRemoveTimeStr = tempRemoveTimeStr;
// Save the most recent addition for the overall table role categorization
if (!addTime) {
addTime = currentAddTime;
isSelfAssign = (e.user === user);
}
// Fallback: Check expiry in metadata if explicit removal log is missing
const metadata = p.newmetadata || [];
const cuMeta = metadata.find(m => m.group === 'checkuser');
if (cuMeta && cuMeta.expiry && cuMeta.expiry !== 'infinity' && tempRemoveTimeStr === "Not removed") {
metadataRemoveTime = new Date(cuMeta.expiry);
metadataRemoveTimeStr = cuMeta.expiry.replace('T', ' ').replace('Z', '');
if (!removeTime) removeTime = metadataRemoveTime;
}
// Calculate duration for this specific 1-line pair
let pairDuration = "Unknown";
if (metadataRemoveTime) {
const diffMs = Math.abs(metadataRemoveTime - currentAddTime);
const diffMins = Math.round(diffMs / 60000); // CHANGE: Rounds to the nearest whole minute
const days = Math.floor(diffMins / 1440);
const hours = Math.floor((diffMins % 1440) / 60);
const mins = diffMins % 60;
if (days > 0) pairDuration = `${days}d ${hours}h ${mins}m`;
else if (hours > 0) pairDuration = `${hours}h ${mins}m`;
else if (mins > 0) pairDuration = `${mins}m`;
else pairDuration = "<1m";
// CHANGE: Track the total number of assignments and store the absolute longest duration
assignCount++;
if (diffMins > maxDurationMins) {
maxDurationMins = diffMins;
longestTimeStr = pairDuration;
}
}
// Push the combined 1-line format to the log using commas
filtered.push(`* Added: ${exactTimeLog}, Removed: ${metadataRemoveTimeStr}, Duration: ${pairDuration}`);
// Reset pair variables for the next older event in the loop
tempRemoveTime = null;
tempRemoveTimeStr = "Not removed";
}
});
// Edge case: If a removal was found but the addition happened before the audit period
if (tempRemoveTime && tempRemoveTimeStr !== "Not removed") {
filtered.push(`* Added: Before audit period, Removed: ${tempRemoveTimeStr}, Duration: Unknown`);
}
// Joins logs with newlines for the report
if (filtered.length) {
logText = '\n' + filtered.join('\n');
}
} catch (err) { /* Rights log query failed */ }
// --- GLOBAL HISTORY CHECK (WITH CACHE) ---
if (!historyCache[user]) {
if (!isGloballySteward || !isGloballyStaff || !isGloballyOmbuds) {
// Check Meta-Wiki history logs only if user is missing at least one global role today
historyCache[user] = await checkGlobalHistory(user, start, end);
} else {
historyCache[user] = { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
const history = historyCache[user];
// --- HIERARCHY LOGIC (CheckUser Audit Focus) ---
let roleLabel = "";
// 1. STAFF (Highest audit priority for WMF accountability)
if (isGloballyStaff || history.wasStaff) {
roleLabel = isGloballyStaff ? "Current Staff" : "Former Staff (in period)";
}
// 2. LOCAL CHECKUSER (Overrides Steward/Ombudsman roles if local rights exist)
else if (isCurrentLocal) {
roleLabel = "Current Local CheckUser";
}
// 3. STEWARD - SPECIFIC INTERVENTION (Using exact self-assignment duration or max duration if multiple)
else if (longestTimeStr && isSelfAssign) {
if (assignCount > 1) {
roleLabel = `Steward action (Self-assign: longest ${longestTimeStr}, see log)`;
} else {
roleLabel = `Steward action (Self-assign: ${longestTimeStr})`;
}
}
// 4. STEWARD - GENERAL (Global rights usage on external wikis)
else if (isGloballySteward || history.wasSteward) {
roleLabel = isGloballySteward ? "Current Steward" : "Former Steward (in period)";
}
// 5. OMBUDSMAN (Global oversight role)
else if (isGloballyOmbuds || history.wasOmbuds) {
roleLabel = isGloballyOmbuds ? "Current Ombudsman" : "Former Ombudsman (in period)";
}
// 6. FORMER LOCAL CheckUser (Fallback for those who lost rights but performed actions)
else {
roleLabel = "Former Local CheckUser";
}
return { role: roleLabel, log: logText };
}
async function runAudit(yf, mf, yt, mt) {
isRunning = true;
results = {};
emptyWikis = [];
failedWikis = [];
userCache = {};
historyCache = {};
$('#start').prop('disabled', true);
$('#stop').prop('disabled', false);
$('#out').hide();
// Format ISO timestamps for the API
const START = `${yf}-${mf.toString().padStart(2, '0')}-01T00:00:00Z`;
const lastDay = new Date(Date.UTC(yt, mt, 0)).getUTCDate();
const END = `${yt}-${mt.toString().padStart(2, '0')}-${lastDay.toString().padStart(2, '0')}T23:59:59Z`;
// Generate list of months for dynamic table columns
const monthCols = [];
let currY = parseInt(yf), currM = parseInt(mf);
const endY = parseInt(yt), endM = parseInt(mt);
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
while (currY < endY || (currY === endY && currM <= endM)) {
monthCols.push({
key: `${currY}-${String(currM).padStart(2, '0')}`,
label: `${monthNames[currM - 1]} ${currY}` // e.g., "Jan 2024"
});
currM++;
if (currM > 12) { currM = 1; currY++; }
}
for (let i = 0; i < rawWikis.length && isRunning; i++) {
const db = rawWikis[i];
$('#status-msg').text(`Scanning ${db} (${i + 1}/${rawWikis.length})...`);
let successLocal = false;
let continueToken = null; // Token for pagination
while (!successLocal && isRunning) {
try {
const api = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
let params = {
action: 'query',
list: 'checkuserlog',
culfrom: START,
culto: END,
culdir: 'newer',
cullimit: 'max',
formatversion: 2
};
// Add continue token if we are on page 2 or further
if (continueToken) {
Object.assign(params, continueToken);
}
let res = await api.get(params);
const ent = (res.query && res.query.checkuserlog && res.query.checkuserlog.entries) || [];
if (ent.length) {
if (!results[db]) results[db] = {};
// Sum up actions per user (total and per month)
ent.forEach(e => {
const u = e.checkuser;
if (!results[db][u]) results[db][u] = { total: 0, months: {} };
const date = new Date(e.timestamp);
const mKey = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
results[db][u].total++;
results[db][u].months[mKey] = (results[db][u].months[mKey] || 0) + 1;
});
}
// Pagination check
if (res.continue && isRunning) {
continueToken = res.continue;
$('#status-msg').text(`Scanning ${db} (fetching next page)...`);
} else {
// Wiki scan finished (either no entries at all or all pages fetched)
if (!results[db]) emptyWikis.push(db);
successLocal = true;
}
} catch (err) {
const status = (err && err.xhr) ? err.xhr.status : (err && err.status);
if (status === 429) {
// Rate limit hit, wait 30s and retry the SAME wiki/page
$('#status-msg').text(`Rate limit on ${db}. Waiting 30s...`);
await sleep(30000);
} else {
// Critical error (CORS, DNS, etc.), skip this wiki
console.error(`Skipping ${db} due to error:`, err);
failedWikis.push(db);
successLocal = true;
}
}
}
$('#bar').val(i + 1);
await sleep(DELAY_MS);
}
// --- REPORT GENERATION (Plain Text, Project Totals on TOP) ---
$('#status-msg').text(`Generating report...`);
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' (UTC)';
let wt = `== Global CheckUser Stats (${mf}/${yf} - ${mt}/${yt}) ==\n''Report generated on: ${timestamp}''\n\n`;
if (!isRunning) wt += `'''Note: This is a partial report (stopped manually).'''\n\n`;
let rightsLog = `\n== Rights Log (In Period) ==\n`;
let headerMonths = monthCols.map(m => `!! ${m.label}`).join(' ');
wt += `{| class="wikitable sortable" style="font-size:90%; text-align:right;"\n`;
wt += `! Wiki / User !! Category !! '''Total''' !! per 1k users !! per 1k edits ${headerMonths}\n`;
const sortedDBs = Object.keys(results).sort();
if (sortedDBs.length === 0) wt += `|-\n| colspan="${5 + monthCols.length}" style="text-align:center;" | No actions found.\n`;
for (const db of sortedDBs) {
// Skript počká na AQS data
await sleep(2000);
$('#status-msg').text(`Processing ${db}...`);
const metrics = await fetchWikiMetrics(db, START, END);
// 1.
wt += `|-\n! colspan="${5 + monthCols.length}" style="background:#eaecf0; text-align:center;" | ${db}\n`;
// 2.
let wikiTotalActions = 0;
let wikiMonthlySums = {};
monthCols.forEach(col => wikiMonthlySums[col.key] = 0);
const projectUsers = Object.keys(results[db]).sort();
projectUsers.forEach(u => {
wikiTotalActions += results[db][u].total;
monthCols.forEach(col => {
wikiMonthlySums[col.key] += (results[db][u].months[col.key] || 0);
});
});
let wP1kU = metrics.avgEditors > 0 ? ((wikiTotalActions / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let wP1kE = metrics.totalEdits > 0 ? ((wikiTotalActions / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
// 3.
let totalRow = `|- style="background:#f8f9fa; font-weight:bold;"\n| style="text-align:left;" | TOTAL ${db} || — || ${wikiTotalActions} || ${wP1kU} || ${wP1kE}`;
monthCols.forEach(col => {
totalRow += ` || ${wikiMonthlySums[col.key]}`;
});
wt += `${totalRow}\n`;
// 4.
for (const user of projectUsers) {
const m = await fetchUserData(user, db, START, END);
const uD = results[db][user];
let uP1kU = metrics.avgEditors > 0 ? ((uD.total / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let uP1kE = metrics.totalEdits > 0 ? ((uD.total / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
let row = `|-\n| style="text-align:left;" | ${user} || <small>${m.role}</small> || '''${uD.total}''' || ${uP1kU} || ${uP1kE}`;
monthCols.forEach(col => {
row += ` || ${uD.months[col.key] || 0}`;
});
wt += row + `\n`;
if (m.log) rightsLog += `'''${user}@${db}''':${m.log}\n\n`;
}
}
wt += `|}\n\n== Projects with 0 actions ==\n<div style="font-size:85%; color:#54595d;">${emptyWikis.sort().join(', ')}</div>\n`;
if (failedWikis.length) wt += `\n== Errors ==\n${failedWikis.sort().join(', ')}\n`;
wt += rightsLog + `\n== References ==\n<references />\n`;
$('#out').val(wt).show();
$('#status-msg').text(`Done.`);
$('#start').prop('disabled', false);
$('#stop').prop('disabled', true);
}
setupUI();
});
})();
7sstcnq6jhgdlthsvmxgeynwy84jy2p
737256
737245
2026-04-08T19:15:07Z
MrJaroslavik
44012
737256
javascript
text/javascript
// GlobalCheckUserStats.js
// Features:
// - Global Audit: Scans CheckUser logs across 900+ Wikimedia projects at once.
// - Smart Categorization: Identifies roles (Local CU, Steward, Staff, etc.).
// - Steward Logic: Detects temporary access, exact durations, and longest active periods.
// - Deep Scan: Automated pagination to bypass the 500-entry API limit (essential for enwiki).
// - Robust Connection: Automated retries for 429 rate-limits and custom domain mapping.
// - Full Reporting: Outputs sortable Wikitables and detailed rights change logs.
// - Custom UI: Integrated control panel on [[Special:BlankPage/GlobalCheckUserStats]].
// - With help of Gemini 3
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').indexOf('GlobalCheckUserStats') === -1) return;
const rawWikis = ['abstractwiki','abwiki','acewiki','adywiki','afwiki','afwikibooks','afwikiquote','afwiktionary','alswiki','altwiki','amiwiki','amwiki','amwiktionary','angwiki','angwiktionary','annwiki','anpwiki','anwiki','anwiktionary','arcwiki','arwiki','arwikibooks','arwikimedia','arwikinews','arwikiquote','arwikisource','arwikiversity','arwiktionary','arywiki','arzwiki','astwiki','astwiktionary','aswiki','aswikiquote','aswikisource','atjwiki','avkwiki','avwiki','awawiki','aywiki','aywiktionary','azbwiki','azwiki','azwikibooks','azwikiquote','azwikisource','azwiktionary','banwiki','banwikisource','barwiki','bat_smgwiki','bawiki','bawikibooks','bbcwiki','bclwiki','bclwikiquote','bclwikisource','bclwiktionary','bdrwiki','bdwikimedia','be_x_oldwiki','betawikiversity','bewiki','bewikibooks','bewikimedia','bewikiquote','bewikisource','bewiktionary','bewwiki','bewwiktionary','bgwiki','bgwikibooks','bgwikiquote','bgwikisource','bgwiktionary','bhwiki','biwiki','bjnwiki','bjnwikiquote','bjnwiktionary','blkwiki','blkwiktionary','bmwiki','bnwiki','bnwikibooks','bnwikiquote','bnwikisource','bnwikivoyage','bnwiktionary','bowiki','bpywiki','brwiki','brwikimedia','brwikiquote','brwikisource','brwiktionary','bswiki','bswikibooks','bswikinews','bswikiquote','bswikisource','bswiktionary','btmwiki','btmwiktionary','bugwiki','bxrwiki','cawiki','cawikibooks','cawikimedia','cawikinews','cawikiquote','cawikisource','cawiktionary','cbk_zamwiki','cdowiki','cebwiki','cewiki','chrwiki','chrwiktionary','chwiki','chywiki','ckbwiki','ckbwiktionary','commonswiki','cowiki','cowikimedia','cowiktionary','crhwiki','csbwiki','csbwiktionary','cswiki','cswikibooks','cswikinews','cswikiquote','cswikisource','cswikiversity','cswikivoyage','cswiktionary','cuwiki','cvwiki','cvwikibooks','cywiki','cywikibooks','cywikiquote','cywikisource','cywiktionary','dagwiki','dawiki','dawikibooks','dawikiquote','dawikisource','dawiktionary','dewiki','dewikibooks','dewikinews','dewikiquote','dewikisource','dewikiversity','dewikivoyage','dewiktionary','dgawiki','dinwiki','diqwiki','diqwiktionary','dkwikimedia','dsbwiki','dtpwiki','dtywiki','dvwiki','dvwiktionary','dzwiki','eewiki','elwiki','elwikibooks','elwikinews','elwikiquote','elwikisource','elwikiversity','elwikivoyage','elwiktionary','emlwiki','enwiki','enwikibooks','enwikinews','enwikiquote','enwikisource','enwikiversity','enwikivoyage','enwiktionary','eowiki','eowikibooks','eowikinews','eowikiquote','eowikisource','eowikivoyage','eowiktionary','eswiki','eswikibooks','eswikinews','eswikiquote','eswikisource','eswikiversity','eswikivoyage','eswiktionary','etwiki','etwikibooks','eewikimedia','etwikiquote','etwikisource','etwiktionary','euwiki','euwikibooks','euwikiquote','euwikisource','euwiktionary','extwiki','fatwiki','fawiki','fawikibooks','fawikinews','fawikiquote','fawikisource','fawikivoyage','fawiktionary','ffwiki','fiu_vrowiki','fiwiki','fiwikibooks','fiwikimedia','fiwikinews','fiwikiquote','fiwikisource','fiwikiversity','fiwikivoyage','fiwiktionary','fjwiki','fjwiktionary','fonwiki','foundationwiki','fowiki','fowikisource','fowiktionary','frpwiki','frrwiki','frwiki','frwikibooks','frwikinews','frwikiquote','frwikisource','frwikiversity','frwikivoyage','frwiktionary','furwiki','fywiki','fywikibooks','fywiktionary','gagwiki','ganwiki','gawiki','gawiktionary','gcrwiki','gdwiki','gdwiktionary','glkwiki','glwiki','glwikibooks','glwikiquote','glwikisource','glwiktionary','gnwiki','gnwiktionary','gomwiki','gomwiktionary','gorwiki','gorwikiquote','gorwiktionary','gotwiki','gpewiki','gucwiki','gurwiki','guwiki','guwikiquote','guwikisource','guwiktionary','guwwiki','guwwikinews','guwwikiquote','guwwiktionary','gvwiki','gvwiktionary','hakwiki','hawiki','hawiktionary','hawwiki','hewiki','hewikibooks','hewikinews','hewikiquote','hewikisource','hewikivoyage','hewiktionary','hifwiki','hifwiktionary','hiwiki','hiwikibooks','hiwikiquote','hiwikisource','hiwikiversity','hiwikivoyage','hiwiktionary','hrwiki','hrwikibooks','hrwikiquote','hrwikisource','hrwiktionary','hsbwiki','hsbwiktionary','htwiki','huwiki','huwikibooks','huwikisource','huwiktionary','hywiki','hywikibooks','hywikiquote','hywikisource','hywiktionary','hywwiki','iawiki','iawikibooks','iawiktionary','ibawiki','idwiki','idwikibooks','idwikiquote','idwikisource','idwikivoyage','idwiktionary','iewiki','iewiktionary','iglwiki','igwiki','igwikiquote','igwiktionary','ikwiki','ilowiki','incubatorwiki','inhwiki','iowiki','iowiktionary','iswiki','iswikibooks','iswikiquote','iswikisource','iswiktionary','itwiki','itwikibooks','itwikinews','itwikiquote','itwikisource','itwikiversity','itwikivoyage','itwiktionary','iuwiki','iuwiktionary','jamwiki','jawiki','jawikibooks','jawikinews','jawikisource','jawikiversity','jawikivoyage','jawiktionary','jbowiki','jbowiktionary','jvwiki','jvwikisource','jvwiktionary','kaawiki','kaawiktionary','kabwiki','kaiwiki','kajwiki','kawiki','kawikibooks','kawikiquote','kawikisource','kawiktionary','kbdwiki','kbdwiktionary','kbpwiki','kcgwiki','kcgwiktionary','kgewiki','kgwiki','kiwiki','kkwiki','kkwikibooks','kkwiktionary','klwiktionary','kmwiki','kmwikibooks','kmwiktionary','kncwiki','knwiki','knwikiquote','knwikisource','knwiktionary','koiwiki','kowiki','kowikibooks','kowikinews','kowikiquote','kowikisource','kowikiversity','kowiktionary','krcwiki','kshwiki','kswiki','kswiktionary','kuswiki','kuwiki','kuwikibooks','kuwikiquote','kuwiktionary','kvwiki','kwwiki','kwwiktionary','kywiki','kywikiquote','kywiktionary','labswiki','ladwiki','lawiki','lawikibooks','lawikiquote','lawikisource','lawiktionary','lbewiki','lbwiki','lbwiktionary','lezwiki','lfnwiki','lgwiki','lijwiki','lijwikisource','liwiki','liwikibooks','liwikinews','liwikiquote','liwikisource','liwiktionary','lldwiki','lmowiki','lmowiktionary','lnwiki','lnwiktionary','lowiki','lowiktionary','ltgwiki','ltwiki','ltwikibooks','ltwikiquote','ltwikisource','ltwiktionary','lvwiki','lvwiktionary','madwiki','madwikisource','madwiktionary','maiwiki','map_bmswiki','mdfwiki','mediawikiwiki','metawiki','mgwiki','mgwikibooks','mgwiktionary','mhrwiki','minwiki','minwikibooks','minwikisource','minwiktionary','miwiki','miwiktionary','mkwiki','mkwikibooks','mkwikimedia','mkwikisource','mkwiktionary','mlwiki','mlwikibooks','mlwikiquote','mlwikisource','mlwiktionary','mniwiki','mniwiktionary','mnwiki','mnwiktionary','mnwwiki','mnwwiktionary','moswiki','mrjwiki','mrwiki','mrwikibooks','mrwikiquote','mrwikisource','mrwiktionary','mswiki','mswikibooks','mswikiquote','mswikisource','mswiktionary','mtwiki','mtwiktionary','mwlwiki','mxwikimedia','myvwiki','mywiki','mywikisource','mywiktionary','mznwiki','nahwiki','nahwiktionary','napwiki','napwikisource','nawiktionary','nds_nlwiki','ndswiki','ndswiktionary','newiki','newikibooks','newiktionary','newwiki','niawiki','niawiktionary','nlwiki','nlwikibooks','nlwikimedia','nlwikinews','nlwikiquote','nlwikisource','nlwikivoyage','nlwiktionary','nnwiki','nnwikiquote','nnwiktionary','novwiki','nowiki','nowikibooks','nowikimedia','nowikinews','nowikiquote','nowikisource','nowiktionary','nqowiki','nrmwiki','nrwiki','nsowiki','nupwiki','nvwiki','nycwikimedia','nywiki','ocwiki','ocwikibooks','ocwiktionary','olowiki','omwiki','omwiktionary','orwiki','orwikisource','orwiktionary','oswiki','outreachwiki','pagwiki','pamwiki','papwiki','pawiki','pawikibooks','pawikisource','pawiktionary','pcdwiki','pcmwiki','pcmwikiquote','pdcwiki','pflwiki','piwiki','plwiki','plwikibooks','plwikimedia','plwikinews','plwikiquote','plwikisource','plwikivoyage','plwiktionary','pmswiki','pmswikisource','pnbwiki','pnbwiktionary','pntwiki','pplwiki','pswiki','pswikivoyage','pswiktionary','ptwiki','ptwikibooks','ptwikimedia','ptwikinews','ptwikiquote','ptwikisource','ptwikiversity','ptwikivoyage','ptwiktionary','pwnwiki','quwiktionary','rkiwiki','rmwiki','rmywiki','rnwiki','roa_rupwiki','roa_rupwiktionary','roa_tarawiki','rowiki','rowikibooks','rowikinews','rowikiquote','rowiktionary','rskwiki','ruewiki','ruwiki','ruwikibooks','ruwikinews','ruwikiquote','ruwikisource','ruwikiversity','ruwikivoyage','ruwiktionary','rwwiki','rwwiktionary','sahwiki','sahwikiquote','sahwikisource','satwiki','satwiktionary','sawiki','sawikibooks','sawikiquote','sawikisource','sawiktionary','scnwiki','scnwiktionary','scowiki','scwiki','sdwiki','sdwiktionary','sewiki','sewikimedia','sgwiki','sgwiktionary','shiwiki','shnwiki','shnwikibooks','shnwikinews','shnwikivoyage','shnwiktionary','shwiki','shwiktionary','shywiktionary','simplewiki','simplewiktionary','siwiki','siwikibooks','siwiktionary','skwiki','skrwiki','skrwiktionary','slwiki','slwikibooks','slwikiquote','slwikisource','slwikiversity','slwiktionary','smnwiki','smwiki','smwiktionary','snwiki','sourceswiki','sowiki','sowiktionary','specieswiki','sqwiki','sqwikibooks','sqwikinews','sqwikiquote','sqwiktionary','srnwiki','srwiki','srwikibooks','srwikinews','srwikiquote','srwikisource','srwiktionary','sswiki','sswiktionary','stqwiki','stwiki','stwiktionary','suwiki','suwikiquote','suwikisource','suwiktionary','svwiki','svwikibooks','svwikinews','svwikiquote','svwikisource','svwikiversity','svwikivoyage','svwiktionary','swwiki','swwiktionary','sylwiki','szlwiki','szywiki','tawiki','tawikibooks','tawikinews','tawikiquote','tawikisource','tawiktionary','taywiki','tcywiki','tcywikisource','tcywiktionary','tddwiki','tewiki','tewikibooks','tewikiquote','tewikisource','tewiktionary','tgwiki','tgwikibooks','tgwiktionary','thwiki','thwikibooks','thwikimedia','thwikiquote','thwikisource','thwiktionary','tigwiki','tiwiki','tiwiktionary','tkwiki','tkwiktionary','tlwiki','tlwikibooks','tlwikiquote','tlwikisource','tlwiktionary','tlywiki','tnwiki','tnwiktionary','tokwiki','towiki','tpiwiki','tpiwiktionary','trvwiki','trwiki','trwikibooks','trwikimedia','trwikinews','trwikiquote','trwikisource','trwikivoyage','trwiktionary','tswiki','tswiktionary','ttwiki','ttwikibooks','ttwikiquote','ttwiktionary','tumwiki','twwiki','twwiktionary','tyvwiki','tywiki','uawikimedia','udmwiki','ugwiki','ugwiktionary','ukwiki','ukwikibooks','ukwikinews','ukwikiquote','ukwikisource','ukwikivoyage','ukwiktionary','urwiki','urwikibooks','urwikiquote','urwikisource','urwiktionary','uzwiki','uzwikiquote','uzwiktionary','vecwiki','vecwikisource','vecwiktionary','vepwiki','vewiki','viwiki','viwikibooks','viwikiquote','viwikisource','viwikivoyage','viwiktionary','vlswiki','vowiki','vowiktionary','warwiki','wawiki','wawikisource','wawiktionary','wikidatawiki','wikimaniawiki','wowiki','wowiktionary','wuuwiki','xalwiki','xhwiki','xmfwiki','yiwiki','yiwikisource','yiwiktionary','yowiki','zawiki','zeawiki','zghwiki','zghwiktionary','zh_classicalwiki','zh_min_nanwiki','zh_min_nanwikisource','zh_min_nanwiktionary','zh_yuewiki','zhwiki','zhwikibooks','zhwikinews','zhwikiquote','zhwikisource','zhwikiversity','zhwikivoyage','zhwiktionary','zuwiki','zuwiktionary','test2wiki','testwiki'];
const DELAY_MS = 800; // Safe balanced speed
const sleep = ms => new Promise(r => setTimeout(r, ms));
let userCache = {};
let historyCache = {}; // Added this to prevent the ReferenceError
function getDomain(db) {
const mapping = {
'commonswiki': 'commons.wikimedia.org', 'metawiki': 'meta.wikimedia.org', 'wikidatawiki': 'www.wikidata.org',
'mediawikiwiki': 'www.mediawiki.org', 'sourceswiki': 'wikisource.org', 'foundationwiki': 'foundation.wikimedia.org',
'incubatorwiki': 'incubator.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org',
'be_x_oldwiki': 'be-tarask.wikipedia.org', 'labswiki': 'wikitech.wikimedia.org',
'wikimaniawiki': 'wikimania.wikimedia.org', 'specieswiki': 'species.wikimedia.org',
'abstractwiki': 'abstract.wikipedia.org', 'betawikiversity': 'beta.wikiversity.org',
'mo_wiki': 'ro.wikipedia.org',
'testwiki': 'test.wikipedia.org', 'test2wiki': 'test2.wikipedia.org',
'wikifunctionswiki': 'www.wikifunctions.org',
'testcommonswiki': 'test-commons.wikimedia.org',
'testwikidatawiki': 'test.wikidata.org',
};
if (mapping[db]) return mapping[db];
const name = db.replace(/_/g, '-');
if (name.endsWith('wikisource')) return name.slice(0, -10) + '.wikisource.org';
if (name.endsWith('wikiversity')) return name.slice(0, -11) + '.wikiversity.org';
if (name.endsWith('wiktionary')) return name.slice(0, -10) + '.wiktionary.org';
if (name.endsWith('wikivoyage')) return name.slice(0, -10) + '.wikivoyage.org';
if (name.endsWith('wikibooks')) return name.slice(0, -9) + '.wikibooks.org';
if (name.endsWith('wikiquote')) return name.slice(0, -9) + '.wikiquote.org';
if (name.endsWith('wikinews')) return name.slice(0, -8) + '.wikinews.org';
if (name.endsWith('wikimedia')) return name.slice(0, -9) + '.wikimedia.org';
if (name.endsWith('wiki')) return name.slice(0, -4) + '.wikipedia.org';
return name + '.wikipedia.org';
}
mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi']).then(function() {
const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { anonymous: true });
const now = new Date();
// Added scannedWikis to prevent ReferenceError
let results = {}, emptyWikis = [], failedWikis = [], scannedWikis = [], isRunning = false;
function setupUI() {
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
// CHANGE: Dynamically generate dropdown options for years and months
let yearOpts = '';
for (let y = currentYear; y >= 2005; y--) {
yearOpts += `<option value="${y}">${y}</option>`;
}
let monthOptsFrom = '';
let monthOptsTo = '';
for (let m = 1; m <= 12; m++) {
monthOptsFrom += `<option value="${m}" ${m === 1 ? 'selected' : ''}>${m}</option>`;
monthOptsTo += `<option value="${m}" ${m === currentMonth ? 'selected' : ''}>${m}</option>`;
}
$('#firstHeading').text('GlobalCheckUserStats.js');
$('#mw-content-text').empty().append(`
<div style="border:1px solid #a2a9b1; padding:15px; background:#f8f9fa;">
<h3>Audit Range</h3>
From:
<select id="y-f" style="width:70px;">${yearOpts}</select>
<select id="m-f" style="width:50px;">${monthOptsFrom}</select>
To:
<select id="y-t" style="width:70px;">${yearOpts}</select>
<select id="m-t" style="width:50px;">${monthOptsTo}</select>
<button id="start" class="mw-ui-button mw-ui-progressive" style="margin-left:15px;">Run GlobalCheckUserStats.js</button>
<button id="stop" class="mw-ui-button mw-ui-destructive" disabled>Stop</button>
<div id="status-msg" style="margin-top:10px; font-weight:bold; color:#0056b3;">Ready.</div>
<div style="margin-top:5px;"><progress id="bar" value="0" max="${rawWikis.length}" style="width:100%"></progress></div>
<textarea id="out" style="width:100%; height:450px; margin-top:10px; display:none; font-family:monospace; font-size:12px;"></textarea>
</div>
`);
$('#start').click(() => runAudit($('#y-f').val(), $('#m-f').val(), $('#y-t').val(), $('#m-t').val()));
$('#stop').click(() => isRunning = false);
}
// NEW: Fetch monthly averaged editors and total edits from Wikimedia Analytics API
async function fetchWikiMetrics(db, start, end) {
const project = getDomain(db);
const aqsStart = start.split('T')[0].replace(/-/g, '');
const aqsEnd = end.split('T')[0].replace(/-/g, '');
const editorsUrl = `https://wikimedia.org/api/rest_v1/metrics/editors/aggregate/${project}/all-editor-types/all-page-types/all-activity-levels/monthly/${aqsStart}/${aqsEnd}`;
const editsUrl = `https://wikimedia.org/api/rest_v1/metrics/edits/aggregate/${project}/all-editor-types/all-page-types/monthly/${aqsStart}/${aqsEnd}`;
const fetchWithRetry = async (url) => {
let attempts = 0;
while (attempts < 5) {
try {
const res = await fetch(url);
if (res.status === 429) {
let wait = (attempts + 1) * 7000;
$('#status-msg').text(`AQS limit! Cooling down ${wait/1000}s...`);
await sleep(wait);
attempts++;
continue;
}
if (!res.ok) return null;
return await res.json();
} catch (e) {
attempts++;
await sleep(2000);
}
}
return null;
};
try {
const [edRes, etRes] = await Promise.all([
fetchWithRetry(editorsUrl),
fetchWithRetry(editsUrl)
]);
if (!edRes?.items?.[0]?.results || !etRes?.items?.[0]?.results) {
return { avgEditors: 0, totalEdits: 0 };
}
const editorsResults = edRes?.items?.[0]?.results || [];
const avgEditors = editorsResults.length > 0
? editorsResults.reduce((sum, m) => sum + m.editors, 0) / editorsResults.length
: 0;
const totalEdits = etRes?.items?.[0]?.results
? etRes.items[0].results.reduce((sum, m) => sum + m.edits, 0)
: 0;
return { avgEditors, totalEdits };
} catch (e) {
return { avgEditors: 0, totalEdits: 0 };
}
}
// Helper function to check global rights history on Meta-Wiki
async function checkGlobalHistory(user, start, end) {
try {
const res = await metaApi.get({
action: 'query',
list: 'logevents',
letype: 'gblrights',
letitle: 'User:' + user,
formatversion: 2
});
const logs = res.query.logevents || [];
const auditStart = new Date(start);
const auditEnd = new Date(end);
let wasSteward = false, wasStaff = false, wasOmbuds = false;
for (const e of logs) {
const ts = new Date(e.timestamp);
const p = e.params || {};
const added = p.newGroups || p[1] || [];
const removed = p.oldGroups || p[0] || [];
// If a removal happened AFTER the audit started, the user held the rights during the period
if (ts > auditStart) {
if (removed.includes('steward')) wasSteward = true;
if (removed.includes('staff')) wasStaff = true;
if (removed.includes('ombuds')) wasOmbuds = true;
}
// If an addition happened BEFORE the audit ended, the user held the rights during the period
if (ts < auditEnd) {
if (added.includes('steward')) wasSteward = true;
if (added.includes('staff')) wasStaff = true;
if (added.includes('ombuds')) wasOmbuds = true;
}
}
return { wasSteward, wasStaff, wasOmbuds };
} catch (e) {
// Fallback for API errors
return { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
async function fetchUserData(user, db, start, end) {
// Priority 0: Use cache for current global groups
if (!userCache[user]) {
try {
const gres = await metaApi.get({ action: 'query', meta: 'globaluserinfo', guiprop: 'groups', guiuser: user, formatversion: 2 });
userCache[user] = gres.query.globaluserinfo.groups || [];
} catch(e) { userCache[user] = []; }
}
const gGroups = userCache[user];
const isGloballyStaff = gGroups.includes('staff');
const isGloballySteward = gGroups.includes('steward');
const isGloballyOmbuds = gGroups.includes('ombuds');
// Check current local CheckUser rights
let isCurrentLocal = false;
try {
const localApi = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
const ures = await localApi.get({ action: 'query', list: 'users', ususers: user, usprop: 'groups', formatversion: 2 });
const lGroups = (ures.query.users[0] && ures.query.users[0].groups) || [];
isCurrentLocal = lGroups.includes('checkuser');
} catch(e) { /* API failure fallback */ }
// Rights log analysis for the specific Wiki
const target = 'User:' + user + '@' + db;
let logText = ''; // Default is empty, filled only if there are logs
let addTime = null, removeTime = null, isSelfAssign = false;
// CHANGE: Variables to track the longest duration and the number of actions
let maxDurationMins = -1;
let longestTimeStr = "";
let assignCount = 0;
try {
const res = await metaApi.get({
action: 'query', list: 'logevents', letype: 'rights', letitle: target,
lestart: end, leend: start, ledir: 'older',
lelimit: 'max', // ADDED: Loads up to 500 logs instead of 10
formatversion: 2
});
const events = res.query.logevents || [];
let filtered = [];
// Variables to pair removal and addition (iterating newest to oldest)
let tempRemoveTime = null;
let tempRemoveTimeStr = "Not removed";
events.forEach(e => {
const p = e.params || {};
const cuAdded = (p.add || []).includes('checkuser') || ((p.newgroups || []).includes('checkuser') && !(p.oldgroups || []).includes('checkuser'));
const cuRemoved = (p.remove || []).includes('checkuser') || (!(p.newgroups || []).includes('checkuser') && (p.oldgroups || []).includes('checkuser'));
const exactTimeLog = e.timestamp.replace('T', ' ').replace('Z', '');
if (cuRemoved) {
tempRemoveTime = new Date(e.timestamp);
tempRemoveTimeStr = exactTimeLog;
// Save the most recent removal for the overall table role categorization
if (!removeTime) {
removeTime = tempRemoveTime;
if (e.user === user || !e.user) isSelfAssign = true;
}
}
if (cuAdded) {
const currentAddTime = new Date(e.timestamp);
let metadataRemoveTime = tempRemoveTime, metadataRemoveTimeStr = tempRemoveTimeStr;
if (!addTime) {
addTime = currentAddTime;
// FIXED: Added missing closing parenthesis
isSelfAssign = (e.user === user);
}
// Fallback: Check expiry in metadata if explicit removal log is missing
const metadata = p.newmetadata || [];
const cuMeta = metadata.find(m => m.group === 'checkuser');
if (cuMeta && cuMeta.expiry && cuMeta.expiry !== 'infinity' && tempRemoveTimeStr === "Not removed") {
metadataRemoveTime = new Date(cuMeta.expiry);
metadataRemoveTimeStr = cuMeta.expiry.replace('T', ' ').replace('Z', '');
if (!removeTime) removeTime = metadataRemoveTime;
}
// Calculate duration for this specific 1-line pair
let pairDuration = "Unknown";
if (metadataRemoveTime) {
const diffMs = Math.abs(metadataRemoveTime - currentAddTime);
const diffMins = Math.round(diffMs / 60000); // CHANGE: Rounds to the nearest whole minute
const days = Math.floor(diffMins / 1440);
const hours = Math.floor((diffMins % 1440) / 60);
const mins = diffMins % 60;
if (days > 0) pairDuration = `${days}d ${hours}h ${mins}m`;
else if (hours > 0) pairDuration = `${hours}h ${mins}m`;
else if (mins > 0) pairDuration = `${mins}m`;
else pairDuration = "<1m";
// CHANGE: Track the total number of assignments and store the absolute longest duration
assignCount++;
if (diffMins > maxDurationMins) {
maxDurationMins = diffMins;
longestTimeStr = pairDuration;
}
}
// Push the combined 1-line format to the log using commas
filtered.push(`* Added: ${exactTimeLog}, Removed: ${metadataRemoveTimeStr}, Duration: ${pairDuration}`);
// Reset pair variables for the next older event in the loop
tempRemoveTime = null;
tempRemoveTimeStr = "Not removed";
}
});
// Edge case: If a removal was found but the addition happened before the audit period
if (tempRemoveTime && tempRemoveTimeStr !== "Not removed") {
filtered.push(`* Added: Before audit period, Removed: ${tempRemoveTimeStr}, Duration: Unknown`);
}
// Joins logs with newlines for the report
if (filtered.length) {
logText = '\n' + filtered.join('\n');
}
} catch (err) { /* Rights log query failed */ }
// --- GLOBAL HISTORY CHECK (WITH CACHE) ---
if (!historyCache[user]) {
if (!isGloballySteward || !isGloballyStaff || !isGloballyOmbuds) {
// Check Meta-Wiki history logs only if user is missing at least one global role today
historyCache[user] = await checkGlobalHistory(user, start, end);
} else {
historyCache[user] = { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
const history = historyCache[user];
// --- HIERARCHY LOGIC (CheckUser Audit Focus) ---
let roleLabel = "";
// 1. STAFF (Highest audit priority for WMF accountability)
if (isGloballyStaff || history.wasStaff) {
roleLabel = isGloballyStaff ? "Current Staff" : "Former Staff (in period)";
}
// 2. LOCAL CHECKUSER (Overrides Steward/Ombudsman roles if local rights exist)
else if (isCurrentLocal) {
roleLabel = "Current Local CheckUser";
}
// 3. STEWARD - SPECIFIC INTERVENTION (Using exact self-assignment duration or max duration if multiple)
else if (longestTimeStr && isSelfAssign) {
if (assignCount > 1) {
roleLabel = `Steward action (Self-assign: longest ${longestTimeStr}, see log)`;
} else {
roleLabel = `Steward action (Self-assign: ${longestTimeStr})`;
}
}
// 4. STEWARD - GENERAL (Global rights usage on external wikis)
else if (isGloballySteward || history.wasSteward) {
roleLabel = isGloballySteward ? "Current Steward" : "Former Steward (in period)";
}
// 5. OMBUDSMAN (Global oversight role)
else if (isGloballyOmbuds || history.wasOmbuds) {
roleLabel = isGloballyOmbuds ? "Current Ombudsman" : "Former Ombudsman (in period)";
}
// 6. FORMER LOCAL CheckUser (Fallback for those who lost rights but performed actions)
else {
roleLabel = "Former Local CheckUser";
}
return { role: roleLabel, log: logText };
}
async function runAudit(yf, mf, yt, mt) {
isRunning = true;
// Reset all variables for a clean start
results = {}; emptyWikis = []; failedWikis = []; scannedWikis = []; userCache = {}; historyCache = {};
$('#start').prop('disabled', true); $('#stop').prop('disabled', false); $('#out').hide();
// Format timestamps for API
const START = `${yf}-${String(mf).padStart(2, '0')}-01T00:00:00Z`;
const lastDay = new Date(Date.UTC(yt, mt, 0)).getUTCDate().toString().padStart(2, '0');
const END = `${yt}-${String(mt).padStart(2, '0')}-${lastDay}T23:59:59Z`;
// Generate month columns
const monthCols = [];
let currY = parseInt(yf), currM = parseInt(mf);
const endY = parseInt(yt), endM = parseInt(mt);
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
while (currY < endY || (currY === endY && currM <= endM)) {
monthCols.push({ key: `${currY}-${String(currM).padStart(2, '0')}`, label: `${monthNames[currM - 1]} ${currY}` });
currM++; if (currM > 12) { currM = 1; currY++; }
}
// PHASE 1: Data Scanning
for (let i = 0; i < rawWikis.length; i++) {
if (!isRunning) break; // Stops scanning new wikis if "Stop" is clicked
const db = rawWikis[i];
scannedWikis.push(db); // Record progress
$('#status-msg').text(`Scanning ${db} (${i + 1}/${rawWikis.length})...`);
let successLocal = false, continueToken = null;
while (!successLocal && isRunning) {
try {
const api = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
let params = { action: 'query', list: 'checkuserlog', culfrom: START, culto: END, culdir: 'newer', cullimit: 'max', formatversion: 2 };
if (continueToken) Object.assign(params, continueToken);
let res = await api.get(params);
const ent = res.query?.checkuserlog?.entries || [];
if (ent.length) {
if (!results[db]) results[db] = {};
ent.forEach(e => {
const u = e.checkuser;
if (!results[db][u]) results[db][u] = { total: 0, months: {} };
const mKey = e.timestamp.slice(0, 7); // YYYY-MM
results[db][u].total++; results[db][u].months[mKey] = (results[db][u].months[mKey] || 0) + 1;
});
}
if (res.continue && isRunning) continueToken = res.continue;
else { if (!results[db]) emptyWikis.push(db); successLocal = true; }
} catch (err) { if (err?.status === 429) await sleep(30000); else { failedWikis.push(db); successLocal = true; } }
}
$('#bar').val(i + 1); await sleep(DELAY_MS);
}
// PHASE 2: Report Generation (Runs even after clicking "Stop")
$('#status-msg').text(`Processing metrics for found projects...`);
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' (UTC)';
let wt = `== Global CheckUser Stats (${mf}/${yf} - ${mt}/${yt}) ==\n''Report generated on: ${timestamp}''\n\n`;
// Check if process was interrupted for a partial report note
if (scannedWikis.length < rawWikis.length) {
wt += `'''Note: This is a partial report (stopped manually). Only projects up to ${scannedWikis[scannedWikis.length-1]} were scanned.'''\n\n`;
}
let rightsLog = `\n== Rights Log (In Period) ==\n`;
let headerMonths = monthCols.map(m => `!! ${m.label}`).join(' ');
wt += `{| class="wikitable sortable" style="font-size:90%; text-align:right;"\n! Wiki / User !! Category !! '''Total''' !! per 1k users !! per 1k edits ${headerMonths}\n`;
const sortedDBs = Object.keys(results).sort();
if (sortedDBs.length === 0) wt += `|-\n| colspan="${5 + monthCols.length}" style="text-align:center;" | No actions found in scanned projects.\n`;
for (const db of sortedDBs) {
await sleep(2000); // Respect AQS API limits
$('#status-msg').text(`Calculating totals for ${db}...`);
const metrics = await fetchWikiMetrics(db, START, END);
// Wiki Header (Centered, Plain Text)
wt += `|-\n! colspan="${5 + monthCols.length}" style="background:#eaecf0; text-align:center;" | ${db}\n`;
// Calculate Project Totals first
let wT = 0, wMS = {};
monthCols.forEach(col => wMS[col.key] = 0);
const projectUsers = Object.keys(results[db]).sort();
projectUsers.forEach(u => {
wT += results[db][u].total;
monthCols.forEach(col => { wMS[col.key] += (results[db][u].months[col.key] || 0); });
});
let wP1kU = metrics.avgEditors > 0 ? ((wT / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let wP1kE = metrics.totalEdits > 0 ? ((wT / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
// PROJECT TOTAL ROW (Top of section)
let totalRow = `|- style="background:#f8f9fa; font-weight:bold;"\n| style="text-align:left;" | TOTAL ${db} || — || ${wT} || ${wP1kU} || ${wP1kE}`;
monthCols.forEach(col => totalRow += ` || ${wMS[col.key]}`);
wt += `${totalRow}\n`;
// USER ROWS (Plain Text names)
for (const user of projectUsers) {
const m = await fetchUserData(user, db, START, END);
const uD = results[db][user];
let uP1kU = metrics.avgEditors > 0 ? ((uD.total / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let uP1kE = metrics.totalEdits > 0 ? ((uD.total / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
let row = `|-\n| style="text-align:left;" | ${user} || <small>${m.role}</small> || '''${uD.total}''' || ${uP1kU} || ${uP1kE}`;
monthCols.forEach(col => row += ` || ${uD.months[col.key] || 0}`);
wt += row + `\n`;
if (m.log) rightsLog += `'''${user}@${db}''':${m.log}\n\n`;
}
}
// Finalize report
wt += `|}\n\n== Projects with 0 actions ==\n<div style="font-size:85%; color:#54595d;">${emptyWikis.sort().join(', ')}</div>\n`;
if (failedWikis.length) wt += `\n== Errors ==\n${failedWikis.sort().join(', ')}\n`;
wt += rightsLog + `\n\n<references />\n`;
$('#out').val(wt).show();
$('#status-msg').text(`Done.`); $('#start').prop('disabled', false); $('#stop').prop('disabled', true);
}
setupUI();
});
})();
n9yja0tb6epst9a11m7ox5b9taxpg28
737262
737256
2026-04-08T19:26:01Z
MrJaroslavik
44012
+
737262
javascript
text/javascript
// GlobalCheckUserStats.js
// Features:
// - Global Audit: Scans CheckUser logs across 900+ Wikimedia projects at once.
// - Smart Categorization: Identifies roles (Local CU, Steward, Staff, etc.).
// - Steward Logic: Detects temporary access, exact durations, and longest active periods.
// - Deep Scan: Automated pagination to bypass the 500-entry API limit (essential for enwiki).
// - Robust Connection: Automated retries for 429 rate-limits and custom domain mapping.
// - Full Reporting: Outputs sortable Wikitables and detailed rights change logs.
// - Custom UI: Integrated control panel on [[Special:BlankPage/GlobalCheckUserStats]].
// - With help of Gemini 3
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').indexOf('GlobalCheckUserStats') === -1) return;
const rawWikis = ['abstractwiki','abwiki','acewiki','adywiki','afwiki','afwikibooks','afwikiquote','afwiktionary','alswiki','altwiki','amiwiki','amwiki','amwiktionary','angwiki','angwiktionary','annwiki','anpwiki','anwiki','anwiktionary','arcwiki','arwiki','arwikibooks','arwikimedia','arwikinews','arwikiquote','arwikisource','arwikiversity','arwiktionary','arywiki','arzwiki','astwiki','astwiktionary','aswiki','aswikiquote','aswikisource','atjwiki','avkwiki','avwiki','awawiki','aywiki','aywiktionary','azbwiki','azwiki','azwikibooks','azwikiquote','azwikisource','azwiktionary','banwiki','banwikisource','barwiki','bat_smgwiki','bawiki','bawikibooks','bbcwiki','bclwiki','bclwikiquote','bclwikisource','bclwiktionary','bdrwiki','bdwikimedia','be_x_oldwiki','betawikiversity','bewiki','bewikibooks','bewikimedia','bewikiquote','bewikisource','bewiktionary','bewwiki','bewwiktionary','bgwiki','bgwikibooks','bgwikiquote','bgwikisource','bgwiktionary','bhwiki','biwiki','bjnwiki','bjnwikiquote','bjnwiktionary','blkwiki','blkwiktionary','bmwiki','bnwiki','bnwikibooks','bnwikiquote','bnwikisource','bnwikivoyage','bnwiktionary','bowiki','bpywiki','brwiki','brwikimedia','brwikiquote','brwikisource','brwiktionary','bswiki','bswikibooks','bswikinews','bswikiquote','bswikisource','bswiktionary','btmwiki','btmwiktionary','bugwiki','bxrwiki','cawiki','cawikibooks','cawikimedia','cawikinews','cawikiquote','cawikisource','cawiktionary','cbk_zamwiki','cdowiki','cebwiki','cewiki','chrwiki','chrwiktionary','chwiki','chywiki','ckbwiki','ckbwiktionary','commonswiki','cowiki','cowikimedia','cowiktionary','crhwiki','csbwiki','csbwiktionary','cswiki','cswikibooks','cswikinews','cswikiquote','cswikisource','cswikiversity','cswikivoyage','cswiktionary','cuwiki','cvwiki','cvwikibooks','cywiki','cywikibooks','cywikiquote','cywikisource','cywiktionary','dagwiki','dawiki','dawikibooks','dawikiquote','dawikisource','dawiktionary','dewiki','dewikibooks','dewikinews','dewikiquote','dewikisource','dewikiversity','dewikivoyage','dewiktionary','dgawiki','dinwiki','diqwiki','diqwiktionary','dkwikimedia','dsbwiki','dtpwiki','dtywiki','dvwiki','dvwiktionary','dzwiki','eewiki','elwiki','elwikibooks','elwikinews','elwikiquote','elwikisource','elwikiversity','elwikivoyage','elwiktionary','emlwiki','enwiki','enwikibooks','enwikinews','enwikiquote','enwikisource','enwikiversity','enwikivoyage','enwiktionary','eowiki','eowikibooks','eowikinews','eowikiquote','eowikisource','eowikivoyage','eowiktionary','eswiki','eswikibooks','eswikinews','eswikiquote','eswikisource','eswikiversity','eswikivoyage','eswiktionary','etwiki','etwikibooks','eewikimedia','etwikiquote','etwikisource','etwiktionary','euwiki','euwikibooks','euwikiquote','euwikisource','euwiktionary','extwiki','fatwiki','fawiki','fawikibooks','fawikinews','fawikiquote','fawikisource','fawikivoyage','fawiktionary','ffwiki','fiu_vrowiki','fiwiki','fiwikibooks','fiwikimedia','fiwikinews','fiwikiquote','fiwikisource','fiwikiversity','fiwikivoyage','fiwiktionary','fjwiki','fjwiktionary','fonwiki','foundationwiki','fowiki','fowikisource','fowiktionary','frpwiki','frrwiki','frwiki','frwikibooks','frwikinews','frwikiquote','frwikisource','frwikiversity','frwikivoyage','frwiktionary','furwiki','fywiki','fywikibooks','fywiktionary','gagwiki','ganwiki','gawiki','gawiktionary','gcrwiki','gdwiki','gdwiktionary','glkwiki','glwiki','glwikibooks','glwikiquote','glwikisource','glwiktionary','gnwiki','gnwiktionary','gomwiki','gomwiktionary','gorwiki','gorwikiquote','gorwiktionary','gotwiki','gpewiki','gucwiki','gurwiki','guwiki','guwikiquote','guwikisource','guwiktionary','guwwiki','guwwikinews','guwwikiquote','guwwiktionary','gvwiki','gvwiktionary','hakwiki','hawiki','hawiktionary','hawwiki','hewiki','hewikibooks','hewikinews','hewikiquote','hewikisource','hewikivoyage','hewiktionary','hifwiki','hifwiktionary','hiwiki','hiwikibooks','hiwikiquote','hiwikisource','hiwikiversity','hiwikivoyage','hiwiktionary','hrwiki','hrwikibooks','hrwikiquote','hrwikisource','hrwiktionary','hsbwiki','hsbwiktionary','htwiki','huwiki','huwikibooks','huwikisource','huwiktionary','hywiki','hywikibooks','hywikiquote','hywikisource','hywiktionary','hywwiki','iawiki','iawikibooks','iawiktionary','ibawiki','idwiki','idwikibooks','idwikiquote','idwikisource','idwikivoyage','idwiktionary','iewiki','iewiktionary','iglwiki','igwiki','igwikiquote','igwiktionary','ikwiki','ilowiki','incubatorwiki','inhwiki','iowiki','iowiktionary','iswiki','iswikibooks','iswikiquote','iswikisource','iswiktionary','itwiki','itwikibooks','itwikinews','itwikiquote','itwikisource','itwikiversity','itwikivoyage','itwiktionary','iuwiki','iuwiktionary','jamwiki','jawiki','jawikibooks','jawikinews','jawikisource','jawikiversity','jawikivoyage','jawiktionary','jbowiki','jbowiktionary','jvwiki','jvwikisource','jvwiktionary','kaawiki','kaawiktionary','kabwiki','kaiwiki','kajwiki','kawiki','kawikibooks','kawikiquote','kawikisource','kawiktionary','kbdwiki','kbdwiktionary','kbpwiki','kcgwiki','kcgwiktionary','kgewiki','kgwiki','kiwiki','kkwiki','kkwikibooks','kkwiktionary','klwiktionary','kmwiki','kmwikibooks','kmwiktionary','kncwiki','knwiki','knwikiquote','knwikisource','knwiktionary','koiwiki','kowiki','kowikibooks','kowikinews','kowikiquote','kowikisource','kowikiversity','kowiktionary','krcwiki','kshwiki','kswiki','kswiktionary','kuswiki','kuwiki','kuwikibooks','kuwikiquote','kuwiktionary','kvwiki','kwwiki','kwwiktionary','kywiki','kywikiquote','kywiktionary','labswiki','ladwiki','lawiki','lawikibooks','lawikiquote','lawikisource','lawiktionary','lbewiki','lbwiki','lbwiktionary','lezwiki','lfnwiki','lgwiki','lijwiki','lijwikisource','liwiki','liwikibooks','liwikinews','liwikiquote','liwikisource','liwiktionary','lldwiki','lmowiki','lmowiktionary','lnwiki','lnwiktionary','lowiki','lowiktionary','ltgwiki','ltwiki','ltwikibooks','ltwikiquote','ltwikisource','ltwiktionary','lvwiki','lvwiktionary','madwiki','madwikisource','madwiktionary','maiwiki','map_bmswiki','mdfwiki','mediawikiwiki','metawiki','mgwiki','mgwikibooks','mgwiktionary','mhrwiki','minwiki','minwikibooks','minwikisource','minwiktionary','miwiki','miwiktionary','mkwiki','mkwikibooks','mkwikimedia','mkwikisource','mkwiktionary','mlwiki','mlwikibooks','mlwikiquote','mlwikisource','mlwiktionary','mniwiki','mniwiktionary','mnwiki','mnwiktionary','mnwwiki','mnwwiktionary','moswiki','mrjwiki','mrwiki','mrwikibooks','mrwikiquote','mrwikisource','mrwiktionary','mswiki','mswikibooks','mswikiquote','mswikisource','mswiktionary','mtwiki','mtwiktionary','mwlwiki','mxwikimedia','myvwiki','mywiki','mywikisource','mywiktionary','mznwiki','nahwiki','nahwiktionary','napwiki','napwikisource','nawiktionary','nds_nlwiki','ndswiki','ndswiktionary','newiki','newikibooks','newiktionary','newwiki','niawiki','niawiktionary','nlwiki','nlwikibooks','nlwikimedia','nlwikinews','nlwikiquote','nlwikisource','nlwikivoyage','nlwiktionary','nnwiki','nnwikiquote','nnwiktionary','novwiki','nowiki','nowikibooks','nowikimedia','nowikinews','nowikiquote','nowikisource','nowiktionary','nqowiki','nrmwiki','nrwiki','nsowiki','nupwiki','nvwiki','nycwikimedia','nywiki','ocwiki','ocwikibooks','ocwiktionary','olowiki','omwiki','omwiktionary','orwiki','orwikisource','orwiktionary','oswiki','outreachwiki','pagwiki','pamwiki','papwiki','pawiki','pawikibooks','pawikisource','pawiktionary','pcdwiki','pcmwiki','pcmwikiquote','pdcwiki','pflwiki','piwiki','plwiki','plwikibooks','plwikimedia','plwikinews','plwikiquote','plwikisource','plwikivoyage','plwiktionary','pmswiki','pmswikisource','pnbwiki','pnbwiktionary','pntwiki','pplwiki','pswiki','pswikivoyage','pswiktionary','ptwiki','ptwikibooks','ptwikimedia','ptwikinews','ptwikiquote','ptwikisource','ptwikiversity','ptwikivoyage','ptwiktionary','pwnwiki','quwiktionary','rkiwiki','rmwiki','rmywiki','rnwiki','roa_rupwiki','roa_rupwiktionary','roa_tarawiki','rowiki','rowikibooks','rowikinews','rowikiquote','rowiktionary','rskwiki','ruewiki','ruwiki','ruwikibooks','ruwikinews','ruwikiquote','ruwikisource','ruwikiversity','ruwikivoyage','ruwiktionary','rwwiki','rwwiktionary','sahwiki','sahwikiquote','sahwikisource','satwiki','satwiktionary','sawiki','sawikibooks','sawikiquote','sawikisource','sawiktionary','scnwiki','scnwiktionary','scowiki','scwiki','sdwiki','sdwiktionary','sewiki','sewikimedia','sgwiki','sgwiktionary','shiwiki','shnwiki','shnwikibooks','shnwikinews','shnwikivoyage','shnwiktionary','shwiki','shwiktionary','shywiktionary','simplewiki','simplewiktionary','siwiki','siwikibooks','siwiktionary','skwiki','skrwiki','skrwiktionary','slwiki','slwikibooks','slwikiquote','slwikisource','slwikiversity','slwiktionary','smnwiki','smwiki','smwiktionary','snwiki','sourceswiki','sowiki','sowiktionary','specieswiki','sqwiki','sqwikibooks','sqwikinews','sqwikiquote','sqwiktionary','srnwiki','srwiki','srwikibooks','srwikinews','srwikiquote','srwikisource','srwiktionary','sswiki','sswiktionary','stqwiki','stwiki','stwiktionary','suwiki','suwikiquote','suwikisource','suwiktionary','svwiki','svwikibooks','svwikinews','svwikiquote','svwikisource','svwikiversity','svwikivoyage','svwiktionary','swwiki','swwiktionary','sylwiki','szlwiki','szywiki','tawiki','tawikibooks','tawikinews','tawikiquote','tawikisource','tawiktionary','taywiki','tcywiki','tcywikisource','tcywiktionary','tddwiki','tewiki','tewikibooks','tewikiquote','tewikisource','tewiktionary','tgwiki','tgwikibooks','tgwiktionary','thwiki','thwikibooks','thwikimedia','thwikiquote','thwikisource','thwiktionary','tigwiki','tiwiki','tiwiktionary','tkwiki','tkwiktionary','tlwiki','tlwikibooks','tlwikiquote','tlwikisource','tlwiktionary','tlywiki','tnwiki','tnwiktionary','tokwiki','towiki','tpiwiki','tpiwiktionary','trvwiki','trwiki','trwikibooks','trwikimedia','trwikinews','trwikiquote','trwikisource','trwikivoyage','trwiktionary','tswiki','tswiktionary','ttwiki','ttwikibooks','ttwikiquote','ttwiktionary','tumwiki','twwiki','twwiktionary','tyvwiki','tywiki','uawikimedia','udmwiki','ugwiki','ugwiktionary','ukwiki','ukwikibooks','ukwikinews','ukwikiquote','ukwikisource','ukwikivoyage','ukwiktionary','urwiki','urwikibooks','urwikiquote','urwikisource','urwiktionary','uzwiki','uzwikiquote','uzwiktionary','vecwiki','vecwikisource','vecwiktionary','vepwiki','vewiki','viwiki','viwikibooks','viwikiquote','viwikisource','viwikivoyage','viwiktionary','vlswiki','vowiki','vowiktionary','warwiki','wawiki','wawikisource','wawiktionary','wikidatawiki','wikimaniawiki','wowiki','wowiktionary','wuuwiki','xalwiki','xhwiki','xmfwiki','yiwiki','yiwikisource','yiwiktionary','yowiki','zawiki','zeawiki','zghwiki','zghwiktionary','zh_classicalwiki','zh_min_nanwiki','zh_min_nanwikisource','zh_min_nanwiktionary','zh_yuewiki','zhwiki','zhwikibooks','zhwikinews','zhwikiquote','zhwikisource','zhwikiversity','zhwikivoyage','zhwiktionary','zuwiki','zuwiktionary','test2wiki','testwiki'];
const DELAY_MS = 800; // Safe balanced speed
const sleep = ms => new Promise(r => setTimeout(r, ms));
let userCache = {};
let historyCache = {}; // Added this to prevent the ReferenceError
function getDomain(db) {
const mapping = {
'commonswiki': 'commons.wikimedia.org', 'metawiki': 'meta.wikimedia.org', 'wikidatawiki': 'www.wikidata.org',
'mediawikiwiki': 'www.mediawiki.org', 'sourceswiki': 'wikisource.org', 'foundationwiki': 'foundation.wikimedia.org',
'incubatorwiki': 'incubator.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org',
'be_x_oldwiki': 'be-tarask.wikipedia.org', 'labswiki': 'wikitech.wikimedia.org',
'wikimaniawiki': 'wikimania.wikimedia.org', 'specieswiki': 'species.wikimedia.org',
'abstractwiki': 'abstract.wikipedia.org', 'betawikiversity': 'beta.wikiversity.org',
'mo_wiki': 'ro.wikipedia.org',
'testwiki': 'test.wikipedia.org', 'test2wiki': 'test2.wikipedia.org',
'wikifunctionswiki': 'www.wikifunctions.org',
'testcommonswiki': 'test-commons.wikimedia.org',
'testwikidatawiki': 'test.wikidata.org',
};
if (mapping[db]) return mapping[db];
const name = db.replace(/_/g, '-');
if (name.endsWith('wikisource')) return name.slice(0, -10) + '.wikisource.org';
if (name.endsWith('wikiversity')) return name.slice(0, -11) + '.wikiversity.org';
if (name.endsWith('wiktionary')) return name.slice(0, -10) + '.wiktionary.org';
if (name.endsWith('wikivoyage')) return name.slice(0, -10) + '.wikivoyage.org';
if (name.endsWith('wikibooks')) return name.slice(0, -9) + '.wikibooks.org';
if (name.endsWith('wikiquote')) return name.slice(0, -9) + '.wikiquote.org';
if (name.endsWith('wikinews')) return name.slice(0, -8) + '.wikinews.org';
if (name.endsWith('wikimedia')) return name.slice(0, -9) + '.wikimedia.org';
if (name.endsWith('wiki')) return name.slice(0, -4) + '.wikipedia.org';
return name + '.wikipedia.org';
}
mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi']).then(function() {
const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { anonymous: true });
const now = new Date();
// Added scannedWikis to prevent ReferenceError
let results = {}, emptyWikis = [], failedWikis = [], scannedWikis = [], isRunning = false;
function setupUI() {
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
// CHANGE: Dynamically generate dropdown options for years and months
let yearOpts = '';
for (let y = currentYear; y >= 2005; y--) {
yearOpts += `<option value="${y}">${y}</option>`;
}
let monthOptsFrom = '';
let monthOptsTo = '';
for (let m = 1; m <= 12; m++) {
monthOptsFrom += `<option value="${m}" ${m === 1 ? 'selected' : ''}>${m}</option>`;
monthOptsTo += `<option value="${m}" ${m === 12 ? 'selected' : ''}>${m}</option>`;
}
$('#firstHeading').text('GlobalCheckUserStats.js');
$('#mw-content-text').empty().append(`
<div style="border:1px solid #a2a9b1; padding:15px; background:#f8f9fa;">
<h3>Audit Range</h3>
From:
<select id="y-f" style="width:70px;">${yearOpts}</select>
<select id="m-f" style="width:50px;">${monthOptsFrom}</select>
To:
<select id="y-t" style="width:70px;">${yearOpts}</select>
<select id="m-t" style="width:50px;">${monthOptsTo}</select>
<button id="start" class="mw-ui-button mw-ui-progressive" style="margin-left:15px;">Run GlobalCheckUserStats.js</button>
<button id="stop" class="mw-ui-button mw-ui-destructive" disabled>Stop</button>
<div id="status-msg" style="margin-top:10px; font-weight:bold; color:#0056b3;">Ready.</div>
<div style="margin-top:5px;"><progress id="bar" value="0" max="${rawWikis.length}" style="width:100%"></progress></div>
<textarea id="out" style="width:100%; height:450px; margin-top:10px; display:none; font-family:monospace; font-size:12px;"></textarea>
</div>
`);
$('#start').click(() => runAudit($('#y-f').val(), $('#m-f').val(), $('#y-t').val(), $('#m-t').val()));
$('#stop').click(() => isRunning = false);
}
// NEW: Fetch monthly averaged editors and total edits from Wikimedia Analytics API
async function fetchWikiMetrics(db, start, end) {
const project = getDomain(db);
const aqsStart = start.split('T')[0].replace(/-/g, '');
const aqsEnd = end.split('T')[0].replace(/-/g, '');
const editorsUrl = `https://wikimedia.org/api/rest_v1/metrics/editors/aggregate/${project}/all-editor-types/all-page-types/all-activity-levels/monthly/${aqsStart}/${aqsEnd}`;
const editsUrl = `https://wikimedia.org/api/rest_v1/metrics/edits/aggregate/${project}/all-editor-types/all-page-types/monthly/${aqsStart}/${aqsEnd}`;
const fetchWithRetry = async (url) => {
let attempts = 0;
while (attempts < 5) {
try {
const res = await fetch(url);
if (res.status === 429) {
let wait = (attempts + 1) * 7000;
$('#status-msg').text(`AQS limit! Cooling down ${wait/1000}s...`);
await sleep(wait);
attempts++;
continue;
}
if (!res.ok) return null;
return await res.json();
} catch (e) {
attempts++;
await sleep(2000);
}
}
return null;
};
try {
const [edRes, etRes] = await Promise.all([
fetchWithRetry(editorsUrl),
fetchWithRetry(editsUrl)
]);
if (!edRes?.items?.[0]?.results || !etRes?.items?.[0]?.results) {
return { avgEditors: 0, totalEdits: 0 };
}
const editorsResults = edRes?.items?.[0]?.results || [];
const avgEditors = editorsResults.length > 0
? editorsResults.reduce((sum, m) => sum + m.editors, 0) / editorsResults.length
: 0;
const totalEdits = etRes?.items?.[0]?.results
? etRes.items[0].results.reduce((sum, m) => sum + m.edits, 0)
: 0;
return { avgEditors, totalEdits };
} catch (e) {
return { avgEditors: 0, totalEdits: 0 };
}
}
// Helper function to check global rights history on Meta-Wiki
async function checkGlobalHistory(user, start, end) {
try {
const res = await metaApi.get({
action: 'query',
list: 'logevents',
letype: 'gblrights',
letitle: 'User:' + user,
formatversion: 2
});
const logs = res.query.logevents || [];
const auditStart = new Date(start);
const auditEnd = new Date(end);
let wasSteward = false, wasStaff = false, wasOmbuds = false;
for (const e of logs) {
const ts = new Date(e.timestamp);
const p = e.params || {};
const added = p.newGroups || p[1] || [];
const removed = p.oldGroups || p[0] || [];
// If a removal happened AFTER the audit started, the user held the rights during the period
if (ts > auditStart) {
if (removed.includes('steward')) wasSteward = true;
if (removed.includes('staff')) wasStaff = true;
if (removed.includes('ombuds')) wasOmbuds = true;
}
// If an addition happened BEFORE the audit ended, the user held the rights during the period
if (ts < auditEnd) {
if (added.includes('steward')) wasSteward = true;
if (added.includes('staff')) wasStaff = true;
if (added.includes('ombuds')) wasOmbuds = true;
}
}
return { wasSteward, wasStaff, wasOmbuds };
} catch (e) {
// Fallback for API errors
return { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
async function fetchUserData(user, db, start, end) {
// Priority 0: Use cache for current global groups
if (!userCache[user]) {
try {
const gres = await metaApi.get({ action: 'query', meta: 'globaluserinfo', guiprop: 'groups', guiuser: user, formatversion: 2 });
userCache[user] = gres.query.globaluserinfo.groups || [];
} catch(e) { userCache[user] = []; }
}
const gGroups = userCache[user];
const isGloballyStaff = gGroups.includes('staff');
const isGloballySteward = gGroups.includes('steward');
const isGloballyOmbuds = gGroups.includes('ombuds');
// Check current local CheckUser rights
let isCurrentLocal = false;
try {
const localApi = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
const ures = await localApi.get({ action: 'query', list: 'users', ususers: user, usprop: 'groups', formatversion: 2 });
const lGroups = (ures.query.users[0] && ures.query.users[0].groups) || [];
isCurrentLocal = lGroups.includes('checkuser');
} catch(e) { /* API failure fallback */ }
// Rights log analysis for the specific Wiki
const target = 'User:' + user + '@' + db;
let logText = ''; // Default is empty, filled only if there are logs
let addTime = null, removeTime = null, isSelfAssign = false;
// CHANGE: Variables to track the longest duration and the number of actions
let maxDurationMins = -1;
let longestTimeStr = "";
let assignCount = 0;
try {
const res = await metaApi.get({
action: 'query', list: 'logevents', letype: 'rights', letitle: target,
lestart: end, leend: start, ledir: 'older',
lelimit: 'max', // ADDED: Loads up to 500 logs instead of 10
formatversion: 2
});
const events = res.query.logevents || [];
let filtered = [];
// Variables to pair removal and addition (iterating newest to oldest)
let tempRemoveTime = null;
let tempRemoveTimeStr = "Not removed";
events.forEach(e => {
const p = e.params || {};
const cuAdded = (p.add || []).includes('checkuser') || ((p.newgroups || []).includes('checkuser') && !(p.oldgroups || []).includes('checkuser'));
const cuRemoved = (p.remove || []).includes('checkuser') || (!(p.newgroups || []).includes('checkuser') && (p.oldgroups || []).includes('checkuser'));
const exactTimeLog = e.timestamp.replace('T', ' ').replace('Z', '');
if (cuRemoved) {
tempRemoveTime = new Date(e.timestamp);
tempRemoveTimeStr = exactTimeLog;
// Save the most recent removal for the overall table role categorization
if (!removeTime) {
removeTime = tempRemoveTime;
if (e.user === user || !e.user) isSelfAssign = true;
}
}
if (cuAdded) {
const currentAddTime = new Date(e.timestamp);
let metadataRemoveTime = tempRemoveTime, metadataRemoveTimeStr = tempRemoveTimeStr;
if (!addTime) {
addTime = currentAddTime;
// FIXED: Added missing closing parenthesis
isSelfAssign = (e.user === user);
}
// Fallback: Check expiry in metadata if explicit removal log is missing
const metadata = p.newmetadata || [];
const cuMeta = metadata.find(m => m.group === 'checkuser');
if (cuMeta && cuMeta.expiry && cuMeta.expiry !== 'infinity' && tempRemoveTimeStr === "Not removed") {
metadataRemoveTime = new Date(cuMeta.expiry);
metadataRemoveTimeStr = cuMeta.expiry.replace('T', ' ').replace('Z', '');
if (!removeTime) removeTime = metadataRemoveTime;
}
// Calculate duration for this specific 1-line pair
let pairDuration = "Unknown";
if (metadataRemoveTime) {
const diffMs = Math.abs(metadataRemoveTime - currentAddTime);
const diffMins = Math.round(diffMs / 60000); // CHANGE: Rounds to the nearest whole minute
const days = Math.floor(diffMins / 1440);
const hours = Math.floor((diffMins % 1440) / 60);
const mins = diffMins % 60;
if (days > 0) pairDuration = `${days}d ${hours}h ${mins}m`;
else if (hours > 0) pairDuration = `${hours}h ${mins}m`;
else if (mins > 0) pairDuration = `${mins}m`;
else pairDuration = "<1m";
// CHANGE: Track the total number of assignments and store the absolute longest duration
assignCount++;
if (diffMins > maxDurationMins) {
maxDurationMins = diffMins;
longestTimeStr = pairDuration;
}
}
// Push the combined 1-line format to the log using commas
filtered.push(`* Added: ${exactTimeLog}, Removed: ${metadataRemoveTimeStr}, Duration: ${pairDuration}`);
// Reset pair variables for the next older event in the loop
tempRemoveTime = null;
tempRemoveTimeStr = "Not removed";
}
});
// Edge case: If a removal was found but the addition happened before the audit period
if (tempRemoveTime && tempRemoveTimeStr !== "Not removed") {
filtered.push(`* Added: Before audit period, Removed: ${tempRemoveTimeStr}, Duration: Unknown`);
}
// Joins logs with newlines for the report
if (filtered.length) {
logText = '\n' + filtered.join('\n');
}
} catch (err) { /* Rights log query failed */ }
// --- GLOBAL HISTORY CHECK (WITH CACHE) ---
if (!historyCache[user]) {
if (!isGloballySteward || !isGloballyStaff || !isGloballyOmbuds) {
// Check Meta-Wiki history logs only if user is missing at least one global role today
historyCache[user] = await checkGlobalHistory(user, start, end);
} else {
historyCache[user] = { wasSteward: false, wasStaff: false, wasOmbuds: false };
}
}
const history = historyCache[user];
// --- HIERARCHY LOGIC (CheckUser Audit Focus) ---
let roleLabel = "";
// 1. STAFF (Highest audit priority for WMF accountability)
if (isGloballyStaff || history.wasStaff) {
roleLabel = isGloballyStaff ? "Current Staff" : "Former Staff (in period)";
}
// 2. LOCAL CHECKUSER (Overrides Steward/Ombudsman roles if local rights exist)
else if (isCurrentLocal) {
roleLabel = "Current Local CheckUser";
}
// 3. STEWARD - SPECIFIC INTERVENTION (Using exact self-assignment duration or max duration if multiple)
else if (longestTimeStr && isSelfAssign) {
if (assignCount > 1) {
roleLabel = `Steward action (Self-assign: longest ${longestTimeStr}, see log)`;
} else {
roleLabel = `Steward action (Self-assign: ${longestTimeStr})`;
}
}
// 4. STEWARD - GENERAL (Global rights usage on external wikis)
else if (isGloballySteward || history.wasSteward) {
roleLabel = isGloballySteward ? "Current Steward" : "Former Steward (in period)";
}
// 5. OMBUDSMAN (Global oversight role)
else if (isGloballyOmbuds || history.wasOmbuds) {
roleLabel = isGloballyOmbuds ? "Current Ombudsman" : "Former Ombudsman (in period)";
}
// 6. FORMER LOCAL CheckUser (Fallback for those who lost rights but performed actions)
else {
roleLabel = "Former Local CheckUser";
}
return { role: roleLabel, log: logText };
}
async function runAudit(yf, mf, yt, mt) {
isRunning = true;
// Reset all variables for a clean start
results = {}; emptyWikis = []; failedWikis = []; scannedWikis = []; userCache = {}; historyCache = {};
$('#start').prop('disabled', true); $('#stop').prop('disabled', false); $('#out').hide();
// Format timestamps for API
const START = `${yf}-${String(mf).padStart(2, '0')}-01T00:00:00Z`;
const lastDay = new Date(Date.UTC(yt, mt, 0)).getUTCDate().toString().padStart(2, '0');
const END = `${yt}-${String(mt).padStart(2, '0')}-${lastDay}T23:59:59Z`;
// Generate month columns
const monthCols = [];
let currY = parseInt(yf), currM = parseInt(mf);
const endY = parseInt(yt), endM = parseInt(mt);
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
while (currY < endY || (currY === endY && currM <= endM)) {
monthCols.push({ key: `${currY}-${String(currM).padStart(2, '0')}`, label: `${monthNames[currM - 1]} ${currY}` });
currM++; if (currM > 12) { currM = 1; currY++; }
}
// PHASE 1: Data Scanning
for (let i = 0; i < rawWikis.length; i++) {
if (!isRunning) break; // Stops scanning new wikis if "Stop" is clicked
const db = rawWikis[i];
scannedWikis.push(db); // Record progress
$('#status-msg').text(`Scanning ${db} (${i + 1}/${rawWikis.length})...`);
let successLocal = false, continueToken = null;
while (!successLocal && isRunning) {
try {
const api = new mw.ForeignApi(`https://${getDomain(db)}/w/api.php`);
let params = { action: 'query', list: 'checkuserlog', culfrom: START, culto: END, culdir: 'newer', cullimit: 'max', formatversion: 2 };
if (continueToken) Object.assign(params, continueToken);
let res = await api.get(params);
const ent = res.query?.checkuserlog?.entries || [];
if (ent.length) {
if (!results[db]) results[db] = {};
ent.forEach(e => {
const u = e.checkuser;
if (!results[db][u]) results[db][u] = { total: 0, months: {} };
const mKey = e.timestamp.slice(0, 7); // YYYY-MM
results[db][u].total++; results[db][u].months[mKey] = (results[db][u].months[mKey] || 0) + 1;
});
}
if (res.continue && isRunning) continueToken = res.continue;
else { if (!results[db]) emptyWikis.push(db); successLocal = true; }
} catch (err) { if (err?.status === 429) await sleep(30000); else { failedWikis.push(db); successLocal = true; } }
}
$('#bar').val(i + 1); await sleep(DELAY_MS);
}
// PHASE 2: Report Generation (Runs even after clicking "Stop")
$('#status-msg').text(`Processing metrics for found projects...`);
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' (UTC)';
let wt = `== Global CheckUser Stats (${mf}/${yf} - ${mt}/${yt}) ==\n''Report generated on: ${timestamp}''\n\n`;
// Check if process was interrupted for a partial report note
if (scannedWikis.length < rawWikis.length) {
wt += `'''Note: This is a partial report (stopped manually). Only projects up to ${scannedWikis[scannedWikis.length-1]} were scanned.'''\n\n`;
}
let rightsLog = `\n== Rights Log (In Period) ==\n`;
let headerMonths = monthCols.map(m => `!! ${m.label}`).join(' ');
wt += `{| class="wikitable sortable" style="font-size:90%; text-align:right;"\n! Wiki / User !! Category !! '''Total''' !! per 1k users !! per 1k edits ${headerMonths}\n`;
const sortedDBs = Object.keys(results).sort();
if (sortedDBs.length === 0) wt += `|-\n| colspan="${5 + monthCols.length}" style="text-align:center;" | No actions found in scanned projects.\n`;
for (const db of sortedDBs) {
await sleep(2000); // Respect AQS API limits
$('#status-msg').text(`Calculating totals for ${db}...`);
const metrics = await fetchWikiMetrics(db, START, END);
// Wiki Header (Centered, Plain Text)
wt += `|-\n! colspan="${5 + monthCols.length}" style="background:#eaecf0; text-align:center;" | ${db}\n`;
// Calculate Project Totals first
let wT = 0, wMS = {};
monthCols.forEach(col => wMS[col.key] = 0);
const projectUsers = Object.keys(results[db]).sort();
projectUsers.forEach(u => {
wT += results[db][u].total;
monthCols.forEach(col => { wMS[col.key] += (results[db][u].months[col.key] || 0); });
});
let wP1kU = metrics.avgEditors > 0 ? ((wT / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let wP1kE = metrics.totalEdits > 0 ? ((wT / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
// PROJECT TOTAL ROW (Top of section)
let totalRow = `|- style="background:#f8f9fa; font-weight:bold;"\n| style="text-align:left;" | TOTAL ${db} || — || ${wT} || ${wP1kU} || ${wP1kE}`;
monthCols.forEach(col => totalRow += ` || ${wMS[col.key]}`);
wt += `${totalRow}\n`;
// USER ROWS (Plain Text names)
for (const user of projectUsers) {
const m = await fetchUserData(user, db, START, END);
const uD = results[db][user];
let uP1kU = metrics.avgEditors > 0 ? ((uD.total / metrics.avgEditors) * 1000).toFixed(1) : "0.0";
let uP1kE = metrics.totalEdits > 0 ? ((uD.total / metrics.totalEdits) * 1000).toFixed(2) : "0.00";
let row = `|-\n| style="text-align:left;" | ${user} || <small>${m.role}</small> || '''${uD.total}''' || ${uP1kU} || ${uP1kE}`;
monthCols.forEach(col => row += ` || ${uD.months[col.key] || 0}`);
wt += row + `\n`;
if (m.log) rightsLog += `'''${user}@${db}''':${m.log}\n\n`;
}
}
// Finalize report
wt += `|}\n\n== Projects with 0 actions ==\n<div style="font-size:85%; color:#54595d;">${emptyWikis.sort().join(', ')}</div>\n`;
if (failedWikis.length) wt += `\n== Errors ==\n${failedWikis.sort().join(', ')}\n`;
wt += rightsLog + `\n\n<references />\n`;
$('#out').val(wt).show();
$('#status-msg').text(`Done.`); $('#start').prop('disabled', false); $('#stop').prop('disabled', true);
}
setupUI();
});
})();
n15xddv6918h83m9jpullsdsc51kwqy
User talk:JWBTH/CD test page/comment
3
174723
737186
737158
2026-04-08T14:07:43Z
JWBTH
52211
/* Transcluded comments */ addition: test (-) ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737186
wikitext
text/x-wiki
transcluded comment. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:21, 7 April 2026 (UTC)
:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:22, 7 April 2026 (UTC)
::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:23, 7 April 2026 (UTC)
:::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 05:49, 7 April 2026 (UTC)
::::test
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:18, 7 April 2026 (UTC)
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:12, 7 April 2026 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
: {|
| table
|} [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:35, 7 April 2026 (UTC)
{{text|comment in a template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:30, 7 April 2026 (UTC)}}
:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:31, 7 April 2026 (UTC)
: reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
{{User_talk:JWBTH/CD_test_page/closed|comment in a closed template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:38, 7 April 2026 (UTC)}}
some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text.
{{User_talk:JWBTH/CD_test_page/end closed}}
cminz99zaxuzx818yezdfjfqyjcek1w
737191
737186
2026-04-08T14:15:21Z
JWBTH
52211
/* Transcluded comments */ edit addition ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
737191
wikitext
text/x-wiki
transcluded comment. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:21, 7 April 2026 (UTC)
:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:22, 7 April 2026 (UTC)
:: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:23, 7 April 2026 (UTC)
:::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 05:49, 7 April 2026 (UTC)
::::test
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 06:18, 7 April 2026 (UTC)
::::test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 07:12, 7 April 2026 (UTC)
::: test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 14:07, 8 April 2026 (UTC)
: {|
| table
|} [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 18:35, 7 April 2026 (UTC)
{{text|comment in a template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:30, 7 April 2026 (UTC)}}
:test [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:31, 7 April 2026 (UTC)
: reply [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 11:15, 8 April 2026 (UTC)
{{User_talk:JWBTH/CD_test_page/closed|comment in a closed template. [[User:JWBTH|JWBTH]] ([[User talk:JWBTH|talk]]) 04:38, 7 April 2026 (UTC)}}
some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text. some text.
{{User_talk:JWBTH/CD_test_page/end closed}}
mjmu6fy2orhjimyd5vt6cee73a7w4eb
Wikipedia:Requests/Permissions/Aqurs1 3
4
174740
737174
737144
2026-04-08T13:15:35Z
Barras
6527
d
737174
wikitext
text/x-wiki
{{Request-done|1=[[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>]] 13:15, 8 April 2026 (UTC)|2=Looks good.}}
<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.-->
=== [[User:Aqurs1|Aqurs1]] ===
* {{User3|Aqurs1}}, [[Special:CentralAuth/Aqurs1|global contribs]] 08:47, 8 April 2026 (UTC)
* '''Motive for request:''' Testing abusefilter rules and own scripts.
* '''Requested rights:''' Administrator
* '''Comments:''' <!-- Comments of other users -->
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
{{Request closed}}
jfl7m0ljsviz27tqnue8nlumen5u4le
Wikipedia:Requests/Permissions/Plantaest
4
174742
737177
737154
2026-04-08T13:19:21Z
Xaosflux
101
d
737177
wikitext
text/x-wiki
{{Request-done|1=-- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:19, 8 April 2026 (UTC)|2=Active project admin on another project.}}
=== [[User:Plantaest|Plantaest]] ===
* {{User3|Plantaest}}, [[Special:CentralAuth/Plantaest|global contribs]] 09:07, 8 April 2026 (UTC)
* '''Motive for request:''' Hi, I am Plantaest, an administrator on Vietnamese Wikipedia. I want to have administrator right on testwiki for testing some stuffs. Many thanks! [[User:Plantaest|Plantaest]] ([[User talk:Plantaest|talk]]) 09:07, 8 April 2026 (UTC)
* '''Requested rights:''' Administrator
* '''Comments:''' <!-- Comments of other users -->
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
* @[[User:Barras|Barras]]: Hello, I see you below. Can you help grant the right for me? Thanks! [[User:Plantaest|Plantaest]] ([[User talk:Plantaest|talk]]) 09:15, 8 April 2026 (UTC)
*{{Done}} -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:19, 8 April 2026 (UTC)
7fjgmcapuaoxch8zc42n9kqy6h9ggd6
737179
737177
2026-04-08T13:20:39Z
Xaosflux
101
close
737179
wikitext
text/x-wiki
{{Request-done|1=-- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:19, 8 April 2026 (UTC)|2=Active project admin on another project.}}
=== [[User:Plantaest|Plantaest]] ===
* {{User3|Plantaest}}, [[Special:CentralAuth/Plantaest|global contribs]] 09:07, 8 April 2026 (UTC)
* '''Motive for request:''' Hi, I am Plantaest, an administrator on Vietnamese Wikipedia. I want to have administrator right on testwiki for testing some stuffs. Many thanks! [[User:Plantaest|Plantaest]] ([[User talk:Plantaest|talk]]) 09:07, 8 April 2026 (UTC)
* '''Requested rights:''' Administrator
* '''Comments:''' <!-- Comments of other users -->
<!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! -->
[[Category:!Requests]]
[[Category:Really big category]]
__NOINDEX__
* @[[User:Barras|Barras]]: Hello, I see you below. Can you help grant the right for me? Thanks! [[User:Plantaest|Plantaest]] ([[User talk:Plantaest|talk]]) 09:15, 8 April 2026 (UTC)
*{{Done}} -- [[User:Xaosflux|Xaosflux]] (bar/foo) [[User talk:Xaosflux|talk]] 13:19, 8 April 2026 (UTC)
{{Request closed}}
4eposg5pe204vfx0a5fhdh11e1jojpw
User:ToluAyod/Starter kit/Se o mo
2
174743
737170
2026-04-08T12:46:33Z
ToluAyod
69650
Initialised by StarterKit tool — ready for translation
737170
wikitext
text/x-wiki
<div style="border:1px solid #cef2e0;border-radius:4px;padding:8px;background:#f5fffa;margin-bottom:2px;">
<div style="padding:4px 12px;margin-bottom:8px;border:1px solid #a3bfb1;border-radius:4px;background:#cef2e0;">'''Did you know ...'''</div>
<!-- Update with 3–5 interesting facts from recently created or expanded articles.
No need to update daily — refresh when new articles are added.
To add an image: [[File:Filename.jpg|80px|left|alt=description]] -->
* ... that add an interesting fact and link to a relevant article here?
* ... that add an interesting fact and link to a relevant article here?
* ... that add an interesting fact and link to a relevant article here?
</div>
[[Category:Starter Kit templates]][[Category:Main page templates]]
185eq1k742wroqupsei3ywveh6zrug6
User:ToluAyod/Starter kit/Lo jo Oni
2
174744
737171
2026-04-08T12:48:05Z
ToluAyod
69650
Initialised by StarterKit tool — ready for translation
737171
wikitext
text/x-wiki
<div style="border:1px solid #cedff2;border-radius:4px;padding:8px;background:#f5faff;margin-bottom:2px;">
<div style="padding:4px 12px;margin-bottom:8px;border:1px solid #a3b0bf;border-radius:4px;background:#cedff2;">'''On this day'''</div>
<!-- Add 2–3 historical events relevant to your community or topic area.
No need to update daily — refresh occasionally as your wiki grows.
To add an image: [[File:Filename.jpg|80px|right|alt=description]] -->
Add a date here
* Add a historical event and link to a relevant article here.
* Add a historical event and link to a relevant article here.
</div>
[[Category:Starter Kit templates]][[Category:Main page templates]]
oawkmcoyobjvtr2l018429c6lkvtbv7
MediaWiki:Reportincident-nonemergency-helpmethod-contactadmin
8
174745
737248
2026-04-08T19:00:53Z
CaptainEek
47282
ce test
737248
wikitext
text/x-wiki
[[$1|Contact an Administrator]] for help with urgent or ongoing incidents on-wiki
cbdpm5ukeuypa81zg3jtq59cpgsz4fr
User:AsteraBot/Pages to fix
2
174750
737268
2026-04-08T20:06:21Z
AsteraBot
69554
Bot: updating worklist from CheckWiki
737268
wikitext
text/x-wiki
* [[1-Propanol]]
* [[1956-57 Austrian football championship]]
* [[1957-58 Austrian football championship]]
* [[1958-59 Austrian football championship]]
* [[1959-60 Austrian football championship]]
* [[1960-61 Austrian football championship]]
* [[1961-62 Austrian football championship]]
* [[1962-63 Austrian football championship]]
* [[1963-64 Austrian football championship]]
* [[1966-67 Austrian football championship]]
* [[1967-68 Austrian football championship]]
* [[1969-70 Austrian football championship]]
* [[1971-72 Austrian football championship]]
* [[1972-73 Austrian football championship]]
* [[1973-74 Austrian football championship]]
* [[1974â75 Austrian Cup]]
* [[1979-80 Austrian football championship]]
* [[1980-81 Austrian football championship]]
* [[1981-82 Austrian football championship]]
* [[1982-83 Austrian football championship]]
* [[1983-84 Austrian football championship]]
* [[1984-85 Austrian football championship]]
* [[1985- 86 Austrian football championship]]
* [[1986-87 Austrian football championship]]
* [[1987-88 Austrian football championship]]
* [[1988-89 Austrian football championship]]
* [[1989-90 Austrian football championship]]
* [[1990-91 Austrian football championship]]
* [[1991-92 Austrian football championship]]
* [[1992-93 Austrian football championship]]
* [[1995-96 Austrian football championship]]
* [[1997-98 Austrian football championship]]
* [[1998-99 Austrian football championship]]
* [[1999- 00 Austrian football championship]]
* [[2,2,4-Trimethylpentane]]
* [[2008 baby milk scandal]]
* [[2023 Miami Dolphins season]]
* [[2024 Rafah ambush]]
* [[2024â2025 Canada Post labour dispute]]
* [[2025 FIFA U-17 World Cup]]
* [[2026 SDF missile strikes in Hasakah]]
* [[2C-B]]
* [[3I/ATLAS]]
* [[46 (number)]]
* [[AICEL Shura Hall]]
* [[ANO (political party)]]
* [[Abatacept]]
* [[Abbas Araghchi]]
* [[Abdur Rahman ibn Yusuf Mangera]]
* [[Aboubacar Camara]]
* [[Acton Park, Western Australia]]
* [[Africa Movie Academy Awards]]
* [[Ahmad Vahidi]]
* [[Air Buddies]]
* [[Alessandro Rinaldi]]
* [[Alex Coco]]
* [[Alexander Popovich]]
* [[Alexandra Bridge, Western Australia]]
* [[Alexandra Eala]]
* [[Alfred Hörtnagl]]
* [[Alparslan Baran]]
* [[Alto Pass, Illinois]]
* [[Amatsukami]]
* [[Ambergate, Western Australia]]
* [[American League West]]
* [[Amia (fish)]]
* [[Aminul Islam Bulbul]]
* [[Amir Hatami]]
* [[Amnesia]]
* [[Andamooka]]
* [[Andreas Ogris]]
* [[Angélica Rivera]]
* [[Anniebrook, Western Australia]]
* [[Anrie Chase]]
* [[Ansan]]
* [[Antarctic Plateau]]
* [[Antigonid dynasty]]
* [[Anton Fritsch]]
* [[Anton Wegscheider]]
* [[AppBlock]]
* [[Arcadia 2001]]
* [[Ardyaloon]]
* [[Argyrosomus]]
* [[Argyrosomus japonicus]]
* [[Argyrosomus regius]]
* [[Artigas Base]]
* [[Asgar Ali Qaisar]]
* [[Ashish Vijay Kumar]]
* [[Assyrian rebellion]]
* [[Aston Martin in Formula One]]
* [[Astra Tech]]
* [[Atalanta]]
* [[Atomic radius]]
* [[Atractosteus]]
* [[Attacks on civilians in the Russian invasion of Ukraine]]
* [[Attorney General's Office (United Kingdom)]]
* [[Attorney general]]
* [[Attraction to transgender people]]
* [[Au Sable River (Michigan)]]
* [[Au Sable Township, Iosco County, Michigan]]
* [[Au Sable Township, Roscommon County, Michigan]]
* [[Audi A3]]
* [[Audi A6]]
* [[Audi Q6 e-tron]]
* [[August Blaha]]
* [[August Kraupar]]
* [[August Revolution]]
* [[Augusta, Western Australia]]
* [[Aulorhynchus]]
* [[Australind, Western Australia]]
* [[Australosomus]]
* [[Auto-B-Good]]
* [[Aziz Nasirzadeh]]
* [[Babel (movie)]]
* [[Backergunje]]
* [[Bad (tour)]]
* [[Bad 25]]
* [[Bagdad, Kentucky]]
* [[Bahadur Shah II]]
* [[Bahishti Zewar]]
* [[Bakassi conflict]]
* [[Balignicourt]]
* [[Balistes]]
* [[Balistes vetula]]
* [[Balistoides]]
* [[Balistomorphus]]
* [[Ballota]]
* [[Balnot-sur-Laignes]]
* [[Barcaldine, Queensland]]
* [[Baron of Irrus]]
* [[Base General Bernardo O'Higgins Riquelme]]
* [[Bathylychnops exilis]]
* [[Battle of Dograi]]
* [[Baudin, Western Australia]]
* [[Beachlands, Western Australia]]
* [[Beagle Bay]]
* [[Beasts of the Southern Wild]]
* [[Beirut Nights]]
* [[Beisfjord Bridge]]
* [[Bell MTS]]
* [[Bellingshausen Station]]
* [[Beluga (sturgeon)]]
* [[Berkeley Software Distribution]]
* [[Bermuda Weather Service]]
* [[Bichir]]
* [[Bidyadanga Community]]
* [[Big Mamma]]
* [[Black widow pulsar]]
* [[Blackall]]
* [[Blue weed whiting]]
* [[Bluff Point, Western Australia]]
* [[Bob Kane]]
* [[Bobby Helms]]
* [[Bobrisky]]
* [[Bohuslav]]
* [[Boranup, Western Australia]]
* [[Boreas]]
* [[Boundless Immigration]]
* [[Boxer (dog)]]
* [[Brady Corbet]]
* [[Bramley, Western Australia]]
* [[Brass instrument]]
* [[Breath sounds]]
* [[Brest, France]]
* [[Bridgetown, Western Australia]]
* [[Briton languages]]
* [[Brook stickleback]]
* [[Brown-headed cowbird]]
* [[Bruce Arena]]
* [[Brunt Ice Shelf]]
* [[Bugatti]]
* [[Bunbury, Western Australia (suburb)]]
* [[Bunrei]]
* [[Burnside, Western Australia]]
* [[Burt Lancaster]]
* [[Bus]]
* [[Buttonquail]]
* [[CBS Radio]]
* [[CP Lee]]
* [[Camarones, Chile]]
* [[Cambridgeshire]]
* [[Cameron Ocasio]]
* [[Camooweal]]
* [[Cape Burney, Western Australia]]
* [[Captain Arturo Prat Base]]
* [[Carbon tetrafluoride]]
* [[Carey Park, Western Australia]]
* [[Carlini Base]]
* [[Carolin GröÃinger]]
* [[Casino Royale (book)]]
* [[Catalysis]]
* [[Catherine II of Russia]]
* [[Cation]]
* [[CentOS]]
* [[Cerebellum]]
* [[Chaika (boat)]]
* [[Chalcopyrite]]
* [[Chamba district]]
* [[Chanodichthys]]
* [[Charles Brook, Newfoundland and Labrador]]
* [[Charleville, Queensland]]
* [[Chatte]]
* [[Chemical vapor deposition]]
* [[Cheshire]]
* [[Chifley Tower]]
* [[China Airlines Flight 676]]
* [[China National Space Administration]]
* [[Chingthang Lanthaba]]
* [[Chino, California]]
* [[Chondrosteidae]]
* [[Chopper (movie)]]
* [[Chris Meledandri]]
* [[Christian Kraiger]]
* [[Christopher Dean]]
* [[Christopher Street Day]]
* [[Chun Kuk Do]]
* [[Cimolichthys]]
* [[City of Bunbury]]
* [[City of Busselton]]
* [[City of Greater Geraldton]]
* [[Clan Borthwick]]
* [[Clan O'Donnell]]
* [[Clan Riddell]]
* [[Clarence, Tasmania]]
* [[Clement Bischoff]]
* [[Clinton body count conspiracy theory]]
* [[Clock]]
* [[Clockwork radio]]
* [[Cloncurry, Queensland]]
* [[Clown triggerfish]]
* [[Coats Land]]
* [[Cobitidae]]
* [[Cockatiel]]
* [[College Grove, Western Australia]]
* [[Colonialism]]
* [[Colorado Springs nightclub shooting]]
* [[Comandante Ferraz Antarctic Station]]
* [[Commonwealth realm]]
* [[Communist Party of Bangladesh]]
* [[Comparative Method (in Anthropology)]]
* [[Computational complexity theory]]
* [[Computer forensics]]
* [[Conium]]
* [[Constructed language]]
* [[Cook Out (restaurant)]]
* [[Coolgardie, Western Australia]]
* [[Cooperation]]
* [[Court of public opinion]]
* [[Courtenay, Western Australia]]
* [[Covalent bond]]
* [[Cowaramup, Western Australia]]
* [[Cox's Orange Pippin]]
* [[Crab pulsar]]
* [[Cristhian Bahena Rivera]]
* [[Cross stitch]]
* [[Crowdfunding]]
* [[Crucible]]
* [[Crystal Kay]]
* [[Culter]]
* [[Cunnamulla]]
* [[Cyprinus yilongensis]]
* [[DALL-E]]
* [[DDG (rapper)]]
* [[DFC Prag]]
* [[DJ Akademiks]]
* [[DVD player]]
* [[Daedalichthys]]
* [[Daly Waters]]
* [[Dampier, Western Australia]]
* [[Dangerous: The Short Films]]
* [[Dario Moretti]]
* [[Darko Todorovic]]
* [[Davenport, Western Australia]]
* [[Death Race (2008 movie)]]
* [[Death of Zara Qairina Mahathir]]
* [[Deaths in 1945]]
* [[Deaths in May 2025]]
* [[Deepdale, Western Australia]]
* [[Deepdene, Western Australia]]
* [[Deities of seventeen generations]]
* [[Democratic Autonomous Administration of North and East Syria]]
* [[Democratic backsliding]]
* [[Den Harrow]]
* [[Denmark, Western Australia]]
* [[DennÅ Senshi Porygon]]
* [[Derby, Western Australia]]
* [[Deuterocanonical books]]
* [[Dhaka University of Engineering & Technology, Gazipur]]
* [[DiDuLa]]
* [[Diana Ross Presents The Jackson 5]]
* [[Dimethyldichlorosilane]]
* [[Dirac equation]]
* [[Dirk Nannes]]
* [[Disney Television Animation]]
* [[Division by zero]]
* [[Djarindjin]]
* [[Dodge Ram]]
* [[Dome A]]
* [[Dome C]]
* [[Dome F]]
* [[Donda 2]]
* [[Donkey Kong Bananza]]
* [[Donnybrook, Western Australia]]
* [[Doping in the United States]]
* [[Doraemon: Nobita to Fukkatsu no Hoshi]]
* [[Doric dialect (Scotland)]]
* [[Dorset]]
* [[Douarnenez]]
* [[Drummond Cove, Western Australia]]
* [[Dunsborough, Western Australia]]
* [[Durga]]
* [[Dux Express]]
* [[EUR-ACE label]]
* [[Earl of Rothes]]
* [[East Antarctic Ice Sheet]]
* [[East Augusta, Western Australia]]
* [[East Bunbury, Western Australia]]
* [[East Riding of Yorkshire]]
* [[Easter egg (video games)]]
* [[Eaux-Bonnes]]
* [[Ebenaqua]]
* [[Ebo Noah]]
* [[Eddie Bauer]]
* [[Eddie Hearn]]
* [[Eddie Shore]]
* [[Eduard Bauer]]
* [[Effect]]
* [[Eid al-Adha]]
* [[Electroencephalography]]
* [[Electronic waste]]
* [[Elizabeth Kelly]]
* [[Ellsworth Land]]
* [[Ellsworth Mountains]]
* [[Eloise Yung Shih King]]
* [[ElÃn Elmarsdóttir Van Pelt]]
* [[Emerald Fennell]]
* [[Emperor KÅbun]]
* [[Engelbert Smutny]]
* [[Enrique Collar]]
* [[Entropy]]
* [[Epinephelus]]
* [[Equilibrium]]
* [[Eradu, Western Australia]]
* [[Erich Probst]]
* [[Ernst Kozlicek]]
* [[Ernö Schwarz]]
* [[Erotic literature]]
* [[Eschmeyer nexus]]
* [[Esperanza Base]]
* [[Essex]]
* [[Euthynotus]]
* [[Evening]]
* [[Evertz Pharma]]
* [[Execution]]
* [[Exmouth, Western Australia]]
* [[Exploding Kittens]]
* [[Eyal Zamir]]
* [[FC Hertha Wels]]
* [[Family Jr.]]
* [[Father]]
* [[Favoritner AC]]
* [[Fawzia of Egypt]]
* [[Fazail-e-Amaal]]
* [[Fear & Hunger]]
* [[Ferdinand Popelka]]
* [[Fermentation]]
* [[Fianna]]
* [[FilchnerâRonne Ice Shelf]]
* [[Fiqh]]
* [[First-past-the-post voting]]
* [[Fitzroy Crossing]]
* [[Flag of Austria]]
* [[Flag of Brunei]]
* [[Flag of Croatia]]
* [[Flag of Dominica]]
* [[Flag of Iran]]
* [[Flag of Montenegro]]
* [[Flag of Slovakia]]
* [[Fontaine, Isère]]
* [[Ford Escape]]
* [[Forest Grove, Western Australia]]
* [[Forts in India]]
* [[Francia]]
* [[Francis Bacon (artist)]]
* [[Frankincense]]
* [[Frans Krätzig]]
* [[Franz Aigner]]
* [[Franz Erdl]]
* [[Franz Hanreiter]]
* [[Franz Hasil]]
* [[Franz Hofer]]
* [[Franz Kaspirek]]
* [[Franz Schiemer]]
* [[Franz Schilling]]
* [[Franz Swoboda]]
* [[Freedom 250]]
* [[Friday Plans, Inc.]]
* [[Friedrich Brandstetter]]
* [[Friedrich Wöhler]]
* [[Fuckin' in the Bushes]]
* [[Fuel cell vehicle]]
* [[Funk metal]]
* [[Future]]
* [[Gamma ray]]
* [[Gasterosteoidei]]
* [[Geffen Records]]
* [[Geometry Dash]]
* [[Georg Schors]]
* [[Georgy Zhukov]]
* [[Geraldton (suburb)]]
* [[German Antarctic Receiving Station]]
* [[German papiermark]]
* [[Getz Ice Shelf]]
* [[Ghaznavid Empire]]
* [[Gibson Desert]]
* [[Gillian Ayres]]
* [[Gingerbread Man (fairy tale)]]
* [[Giovanni Messe]]
* [[Glass Heart]]
* [[Glen Iris, Western Australia]]
* [[Glenfield, Western Australia]]
* [[Global Sumud Flotilla]]
* [[Gnarabup, Western Australia]]
* [[Gnowangerup]]
* [[Goddess]]
* [[Gongen]]
* [[Gospel of Thomas]]
* [[Government of France]]
* [[Government of India]]
* [[Government of Nazi Germany]]
* [[Government of Pakistan]]
* [[Government shutdowns in the United States]]
* [[Gracetown, Western Australia]]
* [[Grand Casino Arena]]
* [[Grays Harbor College]]
* [[Great Red Spot]]
* [[Great Saint James]]
* [[Great Wall Station]]
* [[Great hammerhead shark]]
* [[Greater London]]
* [[Greenbone]]
* [[Greenfinch]]
* [[Greenough, Western Australia]]
* [[Greenwich Island]]
* [[Group 14 element]]
* [[Group homomorphism]]
* [[Guacamole]]
* [[Guardians of the Galaxy (TV series)]]
* [[Guido von List]]
* [[Gulen, Norway]]
* [[Gustav Thaler]]
* [[Gwalia, Western Australia]]
* [[Gyrosteus]]
* [[Günther Neukirchner]]
* [[HELLO (BPM) 2026]]
* [[HPV vaccine]]
* [[Habermehl Rock]]
* [[Hairdresser]]
* [[Halden]]
* [[Half Angel (1936 movie)]]
* [[Halley Research Station]]
* [[Halls Creek]]
* [[Hamelin Bay, Western Australia]]
* [[Hampshire]]
* [[Hanafi]]
* [[Hanbali]]
* [[HanjirÅ Asanuma]]
* [[Hans Krankl]]
* [[Happy Pot]]
* [[Harbin Z-9]]
* [[Hareid]]
* [[Harvard, Illinois]]
* [[Hawksbill sea turtle]]
* [[Hayaakitsuhiko and Hayaakitsuhime]]
* [[Hazara]]
* [[Health and appearance of Michael Jackson]]
* [[Heimdal]]
* [[Heimo Pfeifenberger]]
* [[Heinrich Hiltl]]
* [[Heinrich Müller (footballer)]]
* [[Heinrich Retschury]]
* [[Hemsedal]]
* [[Henri Paul]]
* [[Henryk Arctowski Polish Antarctic Station]]
* [[Herbert Gager]]
* [[Herbert Prohaska]]
* [[Herbert Rettensteiner]]
* [[Hereditary Sheriff of Fife]]
* [[Herfried Sabitzer]]
* [[Hermann Stadler]]
* [[Herring cale]]
* [[Hewlett-Packard]]
* [[Hexafluoride]]
* [[Hezi Rash]]
* [[Hikaru Utada]]
* [[Hill Dickinson Stadium]]
* [[Hill of Howth Tramway]]
* [[Hippocratic oath]]
* [[Hirokazu Yasuhara]]
* [[History of colonialism]]
* [[Hole, Norway]]
* [[HomePod]]
* [[Horae]]
* [[Horlivka]]
* [[Horten]]
* [[Hotel Transylvania: Hunting The End]]
* [[Huara]]
* [[Hugh O'Neill, Earl of Tyrone]]
* [[Hughenden, Queensland]]
* [[Human After All]]
* [[Hung Cao]]
* [[Huso]]
* [[Hyden, Western Australia]]
* [[Hypercholesterolemia]]
* [[I'm a Celebrity: Unpacked]]
* [[I'm a Celebrityâ¦Get Me Out of Here! (British TV series 24)]]
* [[IPad Air (7th generation)]]
* [[Ibrahim Sekagya]]
* [[Ice cap climate]]
* [[Ilia Ivanschitz]]
* [[Illegal drugs]]
* [[Ilyushin Il-38]]
* [[Immigration]]
* [[Inch]]
* [[Indian Americans]]
* [[Indianapolis Boulevard]]
* [[Indianapolis International Airport]]
* [[IndiaâNorth Korea relations]]
* [[Influencer]]
* [[Infoglobal I-22 Sikatan]]
* [[Injil]]
* [[Instinctools (company)]]
* [[Intercrural sex]]
* [[Internet forum]]
* [[Invincible (TV series)]]
* [[Iris (goddess)]]
* [[Isca Augusta]]
* [[Ishan Kishan]]
* [[Islam in Austria]]
* [[Islamic State â Syria Province]]
* [[Ismael "El Mayo" Zambada]]
* [[Isotope]]
* [[Israel Railways]]
* [[Italki]]
* [[IÄdır]]
* [[J3 League]]
* [[Ja'far]]
* [[Jacob Rasmussen]]
* [[Jakob Zangerl]]
* [[Jalisco New Generation Cartel]]
* [[Jamal Raisani]]
* [[James Packer]]
* [[Jang Bogo Station]]
* [[Jannik Schuuster]]
* [[January 2024]]
* [[Japanese diaspora]]
* [[Jarasandha]]
* [[Jayne Torvill]]
* [[Jazz fusion]]
* [[Jeonju Shrine]]
* [[Jess Mariano]]
* [[Jevnaker]]
* [[Jewish High Priest]]
* [[Jewish National Front]]
* [[Jigalong]]
* [[Jim Varney]]
* [[Jinshin War]]
* [[JoaquÃn Guzmán López]]
* [[Johann Klima]]
* [[John Chizoba Vincent]]
* [[John Roach]]
* [[John T. Myers]]
* [[John the Apostle]]
* [[Jokela school shooting]]
* [[Joker (The Dark Knight)]]
* [[Jonathan Ive]]
* [[Jonny Greenwood]]
* [[Jose Mari Chan]]
* [[Josef Haist]]
* [[Josef Hamerl]]
* [[Josef Hickersberger]]
* [[Joseph City, Arizona]]
* [[Julia Creek, Queensland]]
* [[Jundah, Queensland]]
* [[József Dunszt]]
* [[KSI vs. Logan Paul II]]
* [[Kailram]]
* [[Kakraita (village)]]
* [[Kaluga (sturgeon)]]
* [[Kalumburu]]
* [[Kapfenberg SV]]
* [[Karl Bortoli]]
* [[Karl Schott]]
* [[Karl Tekusch]]
* [[Karl Zankl]]
* [[Karloo, Western Australia]]
* [[Karpe]]
* [[Kashikuri Shrine]]
* [[Katanning]]
* [[Kate Spade New York]]
* [[Katja Wienerroither]]
* [[Kazimierz Ludwik PiÅsudski]]
* [[Keke Palmer]]
* [[Kelly Scholz]]
* [[Kenne Fant]]
* [[Kenneth Branagh]]
* [[Kent Police]]
* [[Kenyaichthys]]
* [[Kerbal Space Program]]
* [[Kerim Alajbegovic]]
* [[Ketenylidene]]
* [[Kevin Federline]]
* [[Khalil Ahmad Saharanpuri]]
* [[Khanke]]
* [[Khongtekcha]]
* [[Khwarazmian invasion of Oman Gulf (1212-1215)]]
* [[KichijÅji]]
* [[Kigali]]
* [[Killing of Yasser Abu Shabab]]
* [[Kimberley (Western Australia)]]
* [[King George Island (Antarctica)]]
* [[King George V DLR station]]
* [[King Sejong Station]]
* [[Kingoonya]]
* [[Kingsauce]]
* [[Kinue Asanuma]]
* [[Kirby (character)]]
* [[Klepp]]
* [[Knights of the Round Table]]
* [[Knowledge graph]]
* [[Kofun]]
* [[Kojarena, Western Australia]]
* [[Kompira]]
* [[Korean Catholic Association]]
* [[Korean sandlance]]
* [[Kremser SC]]
* [[Kristin Harila]]
* [[Krødsherad]]
* [[Kudardup, Western Australia]]
* [[Kununurra, Western Australia]]
* [[Kuwohi]]
* [[LWVNH v. Kramer]]
* [[La Chapelle-Craonnaise]]
* [[La France insoumise]]
* [[La Tour-du-Pin]]
* [[Labriformes]]
* [[Lagrange, Western Australia]]
* [[Lalish]]
* [[Lambda calculus]]
* [[Lancashire]]
* [[Land Rover]]
* [[Landscape painting]]
* [[Lapstone, New South Wales]]
* [[Lassina Traoré (footballer, born 2007)]]
* [[Latin alphabet]]
* [[Laverton, Western Australia]]
* [[Le Griffon]]
* [[Leaf litter]]
* [[Leeuwin, Western Australia]]
* [[Leeuwin-Naturaliste National Park]]
* [[Leicestershire]]
* [[Leigh Creek, South Australia]]
* [[Lena Mukhina]]
* [[Lent]]
* [[Leonhard Machu]]
* [[Leonora, Western Australia]]
* [[Leopardus]]
* [[Leopold Giebisch]]
* [[Lepisosteiformes]]
* [[Lepisosteus]]
* [[Lesdain]]
* [[Lev Shomea]]
* [[Life Is Beautiful]]
* [[Light truck]]
* [[Lightning Ridge]]
* [[Like Phantoms, Forever]]
* [[Linear Pottery culture]]
* [[Lionel Shapiro]]
* [[List of Dexter's Laboratory episodes]]
* [[List of General Secretaries of the Soviet Union]]
* [[List of Shikinaisha in Shima Province]]
* [[List of barons and lords in Britain and Ireland today]]
* [[List of fugal works by Johann Sebastian Bach]]
* [[List of international goals scored by Sadio Mané]]
* [[List of international matches between Austria and Hungary]]
* [[List of mathematical symbols]]
* [[List of prehistoric bony fish genera]]
* [[List of search engines]]
* [[Little weed whiting]]
* [[Live at Wembley July 16, 1988]]
* [[Lombadina]]
* [[London Eye]]
* [[London Interdisciplinary School]]
* [[Longreach, Queensland]]
* [[Looma]]
* [[Loppa]]
* [[Los Pica Pica]]
* [[Luca Salsi]]
* [[Lucas Reiner]]
* [[Luce (mascot)]]
* [[Lugaid Mac Con]]
* [[Luka Sero]]
* [[Lyndhurst, South Australia]]
* [[Mac. Robertson Land]]
* [[Macquarie Island Station]]
* [[Magadhan Empire]]
* [[Magick]]
* [[Mahadzir Mohd Khir]]
* [[Mahomets Flats, Western Australia]]
* [[Mainland Japan]]
* [[Maitri (research station)]]
* [[Malagueta pepper]]
* [[Maleic anhydride]]
* [[Malik Asghar Ali Qaisar]]
* [[Man or bear]]
* [[Mangar (fish)]]
* [[Manjimup, Western Australia]]
* [[Mansudae]]
* [[Marble Bar]]
* [[Marc Platt (producer)]]
* [[Margaret Tudor]]
* [[Mario Party: The Top 100]]
* [[Mark Goulston]]
* [[Marker, Norway]]
* [[Martock]]
* [[Marwan Hadid]]
* [[Mary Ann Crowe]]
* [[Masala]]
* [[Mataranka, Northern Territory]]
* [[Mathematics education]]
* [[Mathieu Blondeau]]
* [[Matt Blumenthal]]
* [[Matt Katz-Bohen]]
* [[Mawlid]]
* [[Mayker Palacios]]
* [[Mazda CX-30]]
* [[Meeker, Oklahoma]]
* [[Meissa]]
* [[Merredin, Western Australia]]
* [[Meru, Western Australia]]
* [[Metriacanthosaurus]]
* [[Metricup, Western Australia]]
* [[Metropolitan Transportation Authority Police]]
* [[Michael Konsel]]
* [[Michele Ferrero]]
* [[Mid West (Western Australia)]]
* [[Midichlorian]]
* [[Midwest Political Science Association]]
* [[Mihashira Torii]]
* [[Mild intellectual disability]]
* [[Mildura]]
* [[Millennium Prize Problems]]
* [[Mindanao rasbora]]
* [[Minimum wage]]
* [[Minister of Sports]]
* [[Ministry of Education (China)]]
* [[Minnenooka, Western Australia]]
* [[Miracle Musical]]
* [[Mirim Horse Riding Club]]
* [[Mirza Mohammed Athar]]
* [[Mitsumine Shrine]]
* [[Mixed acid]]
* [[Modum]]
* [[Moe]]
* [[Mohammad Bagheri (general)]]
* [[Mohammad Pakpour]]
* [[Mohammed Rafi]]
* [[Molidae]]
* [[Molloy Island, Western Australia]]
* [[Moon wrasse]]
* [[Moresby, Western Australia]]
* [[Morphinan]]
* [[Moscow Center of SPARC Technologies (MCST)]]
* [[Moses Häusler]]
* [[Moss, Norway]]
* [[Mossad]]
* [[Mount Barker, Western Australia]]
* [[Mount Gambier]]
* [[Mount Magnet]]
* [[Mozambique Channel]]
* [[Mritunjay Kumar Tiwary]]
* [[Muhammad Ilyas Kandhlawi]]
* [[Muhammad Khudabanda]]
* [[Muhhadith]]
* [[Multiocular O]]
* [[Mungeranie, South Australia]]
* [[NA-185 Dera Ghazi Khan-II]]
* [[NGC 3521]]
* [[Naguib Sawiris]]
* [[Nancy Ajram]]
* [[Nannup, Western Australia]]
* [[Naomi Seibt]]
* [[Narvik]]
* [[Nasser El Sonbaty]]
* [[National Day Against Homophobia]]
* [[National flower of Uruguay]]
* [[Nelumbo lutea]]
* [[Neo-Mu'tazilites]]
* [[Neopentane]]
* [[Nesbyen]]
* [[Neumayer-III Station]]
* [[Nevis]]
* [[New World Order]]
* [[New Zealand English]]
* [[New shekel]]
* [[New vision]]
* [[Newhall estate]]
* [[Nickel Boys]]
* [[Niederanven]]
* [[Nillup, Western Australia]]
* [[Ninjapromo]]
* [[No quarter]]
* [[Noah Z. Jones]]
* [[Nobel Prize in Chemistry]]
* [[Nokia 5510]]
* [[Nonthaburi province]]
* [[Norbert Katz]]
* [[Nordkapp]]
* [[Nordre Land]]
* [[Nore og Uvdal]]
* [[Norseman, Western Australia]]
* [[North Yorkshire]]
* [[Northampton, Western Australia]]
* [[Notodden]]
* [[Nullagine]]
* [[Number Ones (video)]]
* [[Obaidullah Shadikhel]]
* [[Ocean sunfish]]
* [[Octagonal Kofun]]
* [[Odax]]
* [[Office for the Coordination of Humanitarian Affairs]]
* [[Oklahoma House of Representatives]]
* [[Omori (video game)]]
* [[Onion ring]]
* [[Onium ion]]
* [[Onslow, Western Australia]]
* [[Opel Vectra]]
* [[Oppo F17 Pro]]
* [[Organization of Iranian Kurdistan Struggle]]
* [[Orlando Bloom]]
* [[Orly]]
* [[Ornacieux]]
* [[Osmington, Western Australia]]
* [[Ottoman-Persian Wars]]
* [[Ottoman-Qajar War (1906-1907)]]
* [[Our Lady of Guadalupe]]
* [[Oxfordshire]]
* [[PICO-8]]
* [[Paintbrush]]
* [[Pallid sturgeon]]
* [[Palmer Station]]
* [[Panhandle, Texas]]
* [[Pannawonica]]
* [[Paramabhattaraka]]
* [[Paramount Pictures]]
* [[Paris-East Créteil University]]
* [[Parliament of South Australia]]
* [[Pat Nixon]]
* [[Patriots.eu]]
* [[Paul the Apostle]]
* [[Pear Deck]]
* [[Pelican Point, Western Australia]]
* [[Peloponnese]]
* [[Pemberton, Western Australia]]
* [[Pensacola Mountains]]
* [[Peppa Pig series 3]]
* [[Pepperjack cheese]]
* [[Pereslavl-Zalessky]]
* [[Periodic table]]
* [[Persepolis]]
* [[Peter Paul Rubens]]
* [[Peter Schöttel]]
* [[Pickleball]]
* [[Pierre Aubert]]
* [[Pine Creek, Northern Territory]]
* [[Plan de Rescate de Matemáticas]]
* [[Planettoon Tv]]
* [[Platonic solid]]
* [[Plug-in hybrid]]
* [[Point Samson]]
* [[Pokémon Pokopia]]
* [[Political positions of Robert F. Kennedy Jr.]]
* [[Polycyclic compound]]
* [[Polymerization]]
* [[Poon Hiu-wing]]
* [[Popular Forces]]
* [[Power cut]]
* [[Power factor]]
* [[Powerade]]
* [[Praça Soares Madruga]]
* [[President of Honduras]]
* [[Pressure suit]]
* [[Prevelly, Western Australia]]
* [[Profesor Julio Escudero Base]]
* [[Protarchaeopteryx]]
* [[Protest]]
* [[Protura]]
* [[Pseudoliparis swirei]]
* [[Puisseguin]]
* [[Puppy Bowl]]
* [[Puremba]]
* [[Pyongyang International Airport]]
* [[Quarter tone]]
* [[Quettabyte]]
* [[QuiñonerÃa]]
* [[Qumran]]
* [[R62 (New York City Subway car)]]
* [[RDR2 Locomotives]]
* [[ROM hacking]]
* [[Race with Ryan]]
* [[Radu Theodoru]]
* [[Rahma Riad]]
* [[Rainbow cale]]
* [[Raje]]
* [[Rao Bika]]
* [[Rao Sahab]]
* [[Rasbora]]
* [[Razor]]
* [[Rebel News]]
* [[Redgate, Western Australia]]
* [[Reed pen]]
* [[Rekaz]]
* [[Relayball]]
* [[Repository (data)]]
* [[Resignation of Prince Andrew, Duke of York]]
* [[Reza Pahlavi, Crown Prince of Iran]]
* [[Rhinocerotoid]]
* [[Richmond, Queensland]]
* [[Ridge A]]
* [[Riemannian geometry]]
* [[Risala]]
* [[Robert Frind]]
* [[Robert Körner]]
* [[Robert Pavlicek]]
* [[Robert Sara]]
* [[Roberto Canessa]]
* [[Roberto Rossellini]]
* [[Robotic construction kit]]
* [[Rocco Zikovic]]
* [[Roeburne, Western Australia]]
* [[Rogers, Texas]]
* [[Roiffieux]]
* [[Rollag]]
* [[Rosa Brook, Western Australia]]
* [[Rosa Glen, Western Australia]]
* [[Ross Ice Shelf]]
* [[Round Kofun]]
* [[Royal Challenger Bangalore]]
* [[Rudi Hiden]]
* [[Rudolf Schlauf]]
* [[Rudolf Szanwald]]
* [[Rumble Fighter]]
* [[Runes]]
* [[Russian Republic]]
* [[Rutland]]
* [[Ryan Walters]]
* [[Réti Opening]]
* [[Rüstü Erdogan]]
* [[S. Shamsuddin]]
* [[SANAE IV]]
* [[SAS Viya]]
* [[SC Neusiedl am See]]
* [[SEAT Ibiza]]
* [[SK Vorwärts Steyr]]
* [[SV Gloggnitz]]
* [[Saint-Aubin-Fosse-Louvain]]
* [[Saint-Maurice-de-Beynost]]
* [[Saitama Seibu Lions]]
* [[Salon-de-Provence]]
* [[Sam Thompson]]
* [[Samsung Galaxy S series]]
* [[Samuel Gompers]]
* [[Samuel Lister, 1st Baron Masham]]
* [[San Pedro Claver church]]
* [[San Salvador]]
* [[Sandefjord]]
* [[Sandnes]]
* [[Sanel KuljiÄ]]
* [[Sarah Paine]]
* [[Saturn Outlook]]
* [[School year]]
* [[Schroeder, Western Australia]]
* [[Scott Base]]
* [[Scott River East, Western Australia]]
* [[Scott River, Western Australia]]
* [[Scottish clan]]
* [[Scramble for Africa]]
* [[Second Link Expressway]]
* [[Sero Brothers]]
* [[Sex therapy]]
* [[Shackleton Ice Shelf]]
* [[Shaggs' Own Thing]]
* [[Shaman]]
* [[Shangani Patrol]]
* [[Shiba Inu]]
* [[Shinto honorifics]]
* [[Shire of Augusta-Margaret River]]
* [[Shorts]]
* [[Shropshire]]
* [[Siege of Kinsale]]
* [[Sigdal]]
* [[Silver]]
* [[Silver bromide]]
* [[Silver cyanate]]
* [[Silverton, New South Wales]]
* [[Sinn Féin]]
* [[Sky Diver]]
* [[Sleeping Dogs]]
* [[Social Democratic Party (UK, 1990)]]
* [[Sogndal]]
* [[Soju]]
* [[Solanki Rajputs]]
* [[Somerset]]
* [[Soraya Esfandiary-Bakhtiary]]
* [[Sota Kitano]]
* [[Sour sugar]]
* [[South Bunbury, Western Australia]]
* [[South Burlington, Vermont]]
* [[South Geomagnetic Pole]]
* [[South Lebanon conflict (1985â2000)]]
* [[South Orkney Islands]]
* [[South Shetland Islands]]
* [[Southern lapwing]]
* [[Soviet Civil Administration]]
* [[Spanaway Junior High School shooting]]
* [[Square Kofun]]
* [[Staffordshire]]
* [[Standing Council of Scottish Chiefs]]
* [[Stavanger]]
* [[Stealth aircraft]]
* [[Stefan Skoumal]]
* [[Stefan Wagner]]
* [[Stefania Rivi]]
* [[Stereographic projection]]
* [[Steven V. Maksin]]
* [[Stjørdal]]
* [[Stoke Lucero]]
* [[Strong Bad's Cool Game for Attractive People]]
* [[Stygimoloch]]
* [[Sula, Møre og Romsdal]]
* [[Sulfoxide]]
* [[Sunella]]
* [[Sunnah]]
* [[Sunndal]]
* [[Super Animal Royale]]
* [[Super Mario Galaxy]]
* [[Suroz]]
* [[Surrey]]
* [[Susan Monarez]]
* [[Suzzane Cryer]]
* [[Svetlana Kuznetsova]]
* [[Syr Darya sturgeon]]
* [[São Paulo Metro]]
* [[São Paulo/Guarulhos International Airport]]
* [[Ségolène Royal]]
* [[Søndre Land]]
* [[TI-83 series]]
* [[TIC-80]]
* [[Tachycardia]]
* [[Tainrakuasuchus bellator]]
* [[Takashi Iizuka]]
* [[Takifugu]]
* [[Tamil Nadu]]
* [[Tampongate]]
* [[Tangkap Najib rally]]
* [[Tanto]]
* [[Taobao]]
* [[Tarzan (1999 movie)]]
* [[Tatsumi Kimishima]]
* [[Taurus (constellation)]]
* [[Te Wahipounamu]]
* [[Television network]]
* [[Telltale Incorporated]]
* [[Temple Terrace, Florida]]
* [[Teriberka]]
* [[Terra Nova Bay]]
* [[Territorial integrity]]
* [[Terry stop]]
* [[Tessellation]]
* [[Testaments of the Twelve Patriarchs]]
* [[The Ashes]]
* [[The Boy in the Striped Pyjamas]]
* [[The Brink's Job]]
* [[The Burren]]
* [[The Cat in the Hat (movie)]]
* [[The Charlie Brown and Snoopy Show]]
* [[The Dead Dance]]
* [[The Escapists]]
* [[The First Epistle of Clement to the Corinthians]]
* [[The Happy Lovers]]
* [[The Kleptocrats]]
* [[The Matrix Resurrections]]
* [[The Right Excellent]]
* [[The Taking of Pelham 123 (2009 movie)]]
* [[The Tree of Life (movie)]]
* [[The Twigs]]
* [[The Wordies]]
* [[Theodore Tugboat]]
* [[Thimphu]]
* [[Thirsk]]
* [[Thomas Starzl]]
* [[Thorn (letter)]]
* [[Three Munakata goddesses]]
* [[Three-spined stickleback]]
* [[Tilpa]]
* [[Tim Stokely]]
* [[Tincinae]]
* [[Titan triggerfish]]
* [[Titin]]
* [[Tom Price, Western Australia]]
* [[Tonpa Shenrab Miwoche]]
* [[Tony Gonzalez]]
* [[Tony Robbins]]
* [[Toretsk]]
* [[Toronto Argonauts]]
* [[Torre Rise]]
* [[Tory Green]]
* [[ToyÅke Åmikami]]
* [[Traditionalist theology (Islam)]]
* [[Tree (data structure)]]
* [[Treeton, Western Australia]]
* [[Trent Boult]]
* [[Triggerfish]]
* [[Triple Entente]]
* [[Tsukinami-no-Matsuri]]
* [[Tubemouth]]
* [[Tujunga Wash]]
* [[Tulsa King]]
* [[Tupolev Tu-124]]
* [[Tur Abdin]]
* [[Turkish war crimes]]
* [[Turoyo language]]
* [[Tyndall effect]]
* [[Tyne and Wear]]
* [[UEFA Women's Euro 2013]]
* [[USA (disambiguation)]]
* [[Umme Hassan]]
* [[Union Gurten]]
* [[Union Kleinmünchen]]
* [[United Nations Environment Programme]]
* [[United Parcel Service]]
* [[United States Associate Attorney General]]
* [[United States Department of Health and Human Services]]
* [[United States Department of Homeland Security]]
* [[United States Department of Justice]]
* [[United States Department of State]]
* [[United States Department of the Air Force]]
* [[United States Department of the Navy]]
* [[United States Deputy Attorney General]]
* [[United States Under Secretary of the Navy]]
* [[United States invasion of Grenada]]
* [[Universidade Regional do Cariri]]
* [[University of Indonesia]]
* [[University of Nevada, Las Vegas]]
* [[Unzen Onsen Shrine]]
* [[Urban area]]
* [[Usher, Western Australia]]
* [[Uttar Pradesh]]
* [[VTR (Chile)]]
* [[Vagabonds!]]
* [[Valea Moldovei]]
* [[Van, Turkey]]
* [[Vasishthiputra Vasusena]]
* [[Vestre Toten]]
* [[Vestvågøy]]
* [[Victoria Land]]
* [[Vietnam National University, Hanoi]]
* [[Vietnamese food]]
* [[Villa Las Estrellas]]
* [[Vimba elongata]]
* [[Vinje]]
* [[Vinson Elkins]]
* [[Vinyl norbornene]]
* [[Violence against men]]
* [[Violetta Shostak]]
* [[Visionary: The Video Singles]]
* [[VistulaâOder offensive]]
* [[Vittoria, Western Australia]]
* [[Volda]]
* [[Volkswagen T-Cross]]
* [[Volkswagen T-Roc]]
* [[Volkswagen Taigo]]
* [[Volvo XC40]]
* [[Walter Kogler]]
* [[Walter Skocik]]
* [[Wanaaring, New South Wales]]
* [[Warmun Community, Western Australia]]
* [[Warner Glen, Western Australia]]
* [[Warwickshire]]
* [[We (Cyrillic)]]
* [[Weaponization of antisemitism]]
* [[Web browser]]
* [[Wedding of Prince Charles and Lady Diana Spencer]]
* [[Wedgie]]
* [[Welfare state]]
* [[Welsh Football League]]
* [[Wenzhou]]
* [[West Antarctic Ice Sheet]]
* [[West Antarctica]]
* [[West Midlands (county)]]
* [[White Cliffs, New South Wales]]
* [[Wickham, Western Australia]]
* [[Wiener Sport-Club]]
* [[Wilcannia]]
* [[Wilkes Land]]
* [[William Creek, South Australia]]
* [[Wiltshire]]
* [[Windorah]]
* [[Winifred Atwell]]
* [[Winton, Queensland]]
* [[Witchcliffe, Western Australia]]
* [[Withers, Western Australia]]
* [[Wolfgang Mair]]
* [[Woomera, South Australia]]
* [[Worcestershire]]
* [[Wyndham, Western Australia]]
* [[X&Y (album)]]
* [[Xinhua Bookstore]]
* [[YSCC Yokohama]]
* [[Yachad (political party)]]
* [[Yadav]]
* [[Yamato Åkunitama Shrine]]
* [[Yasoob Abbas]]
* [[Yeat]]
* [[Yebble, Western Australia]]
* [[Yumna Marwan]]
* [[Yungngora]]
* [[Yunus Jaunpuri]]
* [[Yuri (manga)]]
* [[Zabeel Stadium]]
* [[Zakariyya Kandhlawi]]
* [[Zezé Procópio]]
* [[Zinzi Coogler]]
* [[Zlatoust]]
* [[Zucchelli Station]]
* [[Zune Software]]
* [[Ãmilienne Malfatto]]
* [[Ãvreux]]
* [[Ãvre Eiker]]
* [[Åharae no Kotoba]]
* [[Åmiya-no-Me]]
* [[ÅÅ«nyatÄ]]
* [[Åemdinli]]
phpg1920yh05guvnt1ni5lkpg0etnqe
737269
737268
2026-04-08T20:09:11Z
Asteralee
69542
testing again
737269
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
737270
737269
2026-04-08T20:11:31Z
AsteraBot
69554
Bot: updating worklist from CheckWiki
737270
wikitext
text/x-wiki
* [[1-Propanol]]
* [[1956-57 Austrian football championship]]
* [[1957-58 Austrian football championship]]
* [[1958-59 Austrian football championship]]
* [[1959-60 Austrian football championship]]
* [[1960-61 Austrian football championship]]
* [[1961-62 Austrian football championship]]
* [[1962-63 Austrian football championship]]
* [[1963-64 Austrian football championship]]
* [[1966-67 Austrian football championship]]
* [[1967-68 Austrian football championship]]
* [[1969-70 Austrian football championship]]
* [[1971-72 Austrian football championship]]
* [[1972-73 Austrian football championship]]
* [[1973-74 Austrian football championship]]
* [[1974–75 Austrian Cup]]
* [[1979-80 Austrian football championship]]
* [[1980-81 Austrian football championship]]
* [[1981-82 Austrian football championship]]
* [[1982-83 Austrian football championship]]
* [[1983-84 Austrian football championship]]
* [[1984-85 Austrian football championship]]
* [[1985- 86 Austrian football championship]]
* [[1986-87 Austrian football championship]]
* [[1987-88 Austrian football championship]]
* [[1988-89 Austrian football championship]]
* [[1989-90 Austrian football championship]]
* [[1990-91 Austrian football championship]]
* [[1991-92 Austrian football championship]]
* [[1992-93 Austrian football championship]]
* [[1995-96 Austrian football championship]]
* [[1997-98 Austrian football championship]]
* [[1998-99 Austrian football championship]]
* [[1999- 00 Austrian football championship]]
* [[2,2,4-Trimethylpentane]]
* [[2008 baby milk scandal]]
* [[2023 Miami Dolphins season]]
* [[2024 Rafah ambush]]
* [[2024–2025 Canada Post labour dispute]]
* [[2025 FIFA U-17 World Cup]]
* [[2026 SDF missile strikes in Hasakah]]
* [[2C-B]]
* [[3I/ATLAS]]
* [[46 (number)]]
* [[AICEL Shura Hall]]
* [[ANO (political party)]]
* [[Abatacept]]
* [[Abbas Araghchi]]
* [[Abdur Rahman ibn Yusuf Mangera]]
* [[Aboubacar Camara]]
* [[Acton Park, Western Australia]]
* [[Africa Movie Academy Awards]]
* [[Ahmad Vahidi]]
* [[Air Buddies]]
* [[Alessandro Rinaldi]]
* [[Alex Coco]]
* [[Alexander Popovich]]
* [[Alexandra Bridge, Western Australia]]
* [[Alexandra Eala]]
* [[Alfred Hörtnagl]]
* [[Alparslan Baran]]
* [[Alto Pass, Illinois]]
* [[Amatsukami]]
* [[Ambergate, Western Australia]]
* [[American League West]]
* [[Amia (fish)]]
* [[Aminul Islam Bulbul]]
* [[Amir Hatami]]
* [[Amnesia]]
* [[Andamooka]]
* [[Andreas Ogris]]
* [[Angélica Rivera]]
* [[Anniebrook, Western Australia]]
* [[Anrie Chase]]
* [[Ansan]]
* [[Antarctic Plateau]]
* [[Antigonid dynasty]]
* [[Anton Fritsch]]
* [[Anton Wegscheider]]
* [[AppBlock]]
* [[Arcadia 2001]]
* [[Ardyaloon]]
* [[Argyrosomus]]
* [[Argyrosomus japonicus]]
* [[Argyrosomus regius]]
* [[Artigas Base]]
* [[Asgar Ali Qaisar]]
* [[Ashish Vijay Kumar]]
* [[Assyrian rebellion]]
* [[Aston Martin in Formula One]]
* [[Astra Tech]]
* [[Atalanta]]
* [[Atomic radius]]
* [[Atractosteus]]
* [[Attacks on civilians in the Russian invasion of Ukraine]]
* [[Attorney General's Office (United Kingdom)]]
* [[Attorney general]]
* [[Attraction to transgender people]]
* [[Au Sable River (Michigan)]]
* [[Au Sable Township, Iosco County, Michigan]]
* [[Au Sable Township, Roscommon County, Michigan]]
* [[Audi A3]]
* [[Audi A6]]
* [[Audi Q6 e-tron]]
* [[August Blaha]]
* [[August Kraupar]]
* [[August Revolution]]
* [[Augusta, Western Australia]]
* [[Aulorhynchus]]
* [[Australind, Western Australia]]
* [[Australosomus]]
* [[Auto-B-Good]]
* [[Aziz Nasirzadeh]]
* [[Babel (movie)]]
* [[Backergunje]]
* [[Bad (tour)]]
* [[Bad 25]]
* [[Bagdad, Kentucky]]
* [[Bahadur Shah II]]
* [[Bahishti Zewar]]
* [[Bakassi conflict]]
* [[Balignicourt]]
* [[Balistes]]
* [[Balistes vetula]]
* [[Balistoides]]
* [[Balistomorphus]]
* [[Ballota]]
* [[Balnot-sur-Laignes]]
* [[Barcaldine, Queensland]]
* [[Baron of Irrus]]
* [[Base General Bernardo O'Higgins Riquelme]]
* [[Bathylychnops exilis]]
* [[Battle of Dograi]]
* [[Baudin, Western Australia]]
* [[Beachlands, Western Australia]]
* [[Beagle Bay]]
* [[Beasts of the Southern Wild]]
* [[Beirut Nights]]
* [[Beisfjord Bridge]]
* [[Bell MTS]]
* [[Bellingshausen Station]]
* [[Beluga (sturgeon)]]
* [[Berkeley Software Distribution]]
* [[Bermuda Weather Service]]
* [[Bichir]]
* [[Bidyadanga Community]]
* [[Big Mamma]]
* [[Black widow pulsar]]
* [[Blackall]]
* [[Blue weed whiting]]
* [[Bluff Point, Western Australia]]
* [[Bob Kane]]
* [[Bobby Helms]]
* [[Bobrisky]]
* [[Bohuslav]]
* [[Boranup, Western Australia]]
* [[Boreas]]
* [[Boundless Immigration]]
* [[Boxer (dog)]]
* [[Brady Corbet]]
* [[Bramley, Western Australia]]
* [[Brass instrument]]
* [[Breath sounds]]
* [[Brest, France]]
* [[Bridgetown, Western Australia]]
* [[Briton languages]]
* [[Brook stickleback]]
* [[Brown-headed cowbird]]
* [[Bruce Arena]]
* [[Brunt Ice Shelf]]
* [[Bugatti]]
* [[Bunbury, Western Australia (suburb)]]
* [[Bunrei]]
* [[Burnside, Western Australia]]
* [[Burt Lancaster]]
* [[Bus]]
* [[Buttonquail]]
* [[CBS Radio]]
* [[CP Lee]]
* [[Camarones, Chile]]
* [[Cambridgeshire]]
* [[Cameron Ocasio]]
* [[Camooweal]]
* [[Cape Burney, Western Australia]]
* [[Captain Arturo Prat Base]]
* [[Carbon tetrafluoride]]
* [[Carey Park, Western Australia]]
* [[Carlini Base]]
* [[Carolin Größinger]]
* [[Casino Royale (book)]]
* [[Catalysis]]
* [[Catherine II of Russia]]
* [[Cation]]
* [[CentOS]]
* [[Cerebellum]]
* [[Chaika (boat)]]
* [[Chalcopyrite]]
* [[Chamba district]]
* [[Chanodichthys]]
* [[Charles Brook, Newfoundland and Labrador]]
* [[Charleville, Queensland]]
* [[Chatte]]
* [[Chemical vapor deposition]]
* [[Cheshire]]
* [[Chifley Tower]]
* [[China Airlines Flight 676]]
* [[China National Space Administration]]
* [[Chingthang Lanthaba]]
* [[Chino, California]]
* [[Chondrosteidae]]
* [[Chopper (movie)]]
* [[Chris Meledandri]]
* [[Christian Kraiger]]
* [[Christopher Dean]]
* [[Christopher Street Day]]
* [[Chun Kuk Do]]
* [[Cimolichthys]]
* [[City of Bunbury]]
* [[City of Busselton]]
* [[City of Greater Geraldton]]
* [[Clan Borthwick]]
* [[Clan O'Donnell]]
* [[Clan Riddell]]
* [[Clarence, Tasmania]]
* [[Clement Bischoff]]
* [[Clinton body count conspiracy theory]]
* [[Clock]]
* [[Clockwork radio]]
* [[Cloncurry, Queensland]]
* [[Clown triggerfish]]
* [[Coats Land]]
* [[Cobitidae]]
* [[Cockatiel]]
* [[College Grove, Western Australia]]
* [[Colonialism]]
* [[Colorado Springs nightclub shooting]]
* [[Comandante Ferraz Antarctic Station]]
* [[Commonwealth realm]]
* [[Communist Party of Bangladesh]]
* [[Comparative Method (in Anthropology)]]
* [[Computational complexity theory]]
* [[Computer forensics]]
* [[Conium]]
* [[Constructed language]]
* [[Cook Out (restaurant)]]
* [[Coolgardie, Western Australia]]
* [[Cooperation]]
* [[Court of public opinion]]
* [[Courtenay, Western Australia]]
* [[Covalent bond]]
* [[Cowaramup, Western Australia]]
* [[Cox's Orange Pippin]]
* [[Crab pulsar]]
* [[Cristhian Bahena Rivera]]
* [[Cross stitch]]
* [[Crowdfunding]]
* [[Crucible]]
* [[Crystal Kay]]
* [[Culter]]
* [[Cunnamulla]]
* [[Cyprinus yilongensis]]
* [[DALL-E]]
* [[DDG (rapper)]]
* [[DFC Prag]]
* [[DJ Akademiks]]
* [[DVD player]]
* [[Daedalichthys]]
* [[Daly Waters]]
* [[Dampier, Western Australia]]
* [[Dangerous: The Short Films]]
* [[Dario Moretti]]
* [[Darko Todorovic]]
* [[Davenport, Western Australia]]
* [[Death Race (2008 movie)]]
* [[Death of Zara Qairina Mahathir]]
* [[Deaths in 1945]]
* [[Deaths in May 2025]]
* [[Deepdale, Western Australia]]
* [[Deepdene, Western Australia]]
* [[Deities of seventeen generations]]
* [[Democratic Autonomous Administration of North and East Syria]]
* [[Democratic backsliding]]
* [[Den Harrow]]
* [[Denmark, Western Australia]]
* [[Dennō Senshi Porygon]]
* [[Derby, Western Australia]]
* [[Deuterocanonical books]]
* [[Dhaka University of Engineering & Technology, Gazipur]]
* [[DiDuLa]]
* [[Diana Ross Presents The Jackson 5]]
* [[Dimethyldichlorosilane]]
* [[Dirac equation]]
* [[Dirk Nannes]]
* [[Disney Television Animation]]
* [[Division by zero]]
* [[Djarindjin]]
* [[Dodge Ram]]
* [[Dome A]]
* [[Dome C]]
* [[Dome F]]
* [[Donda 2]]
* [[Donkey Kong Bananza]]
* [[Donnybrook, Western Australia]]
* [[Doping in the United States]]
* [[Doraemon: Nobita to Fukkatsu no Hoshi]]
* [[Doric dialect (Scotland)]]
* [[Dorset]]
* [[Douarnenez]]
* [[Drummond Cove, Western Australia]]
* [[Dunsborough, Western Australia]]
* [[Durga]]
* [[Dux Express]]
* [[EUR-ACE label]]
* [[Earl of Rothes]]
* [[East Antarctic Ice Sheet]]
* [[East Augusta, Western Australia]]
* [[East Bunbury, Western Australia]]
* [[East Riding of Yorkshire]]
* [[Easter egg (video games)]]
* [[Eaux-Bonnes]]
* [[Ebenaqua]]
* [[Ebo Noah]]
* [[Eddie Bauer]]
* [[Eddie Hearn]]
* [[Eddie Shore]]
* [[Eduard Bauer]]
* [[Effect]]
* [[Eid al-Adha]]
* [[Electroencephalography]]
* [[Electronic waste]]
* [[Elizabeth Kelly]]
* [[Ellsworth Land]]
* [[Ellsworth Mountains]]
* [[Eloise Yung Shih King]]
* [[Elín Elmarsdóttir Van Pelt]]
* [[Emerald Fennell]]
* [[Emperor Kōbun]]
* [[Engelbert Smutny]]
* [[Enrique Collar]]
* [[Entropy]]
* [[Epinephelus]]
* [[Equilibrium]]
* [[Eradu, Western Australia]]
* [[Erich Probst]]
* [[Ernst Kozlicek]]
* [[Ernö Schwarz]]
* [[Erotic literature]]
* [[Eschmeyer nexus]]
* [[Esperanza Base]]
* [[Essex]]
* [[Euthynotus]]
* [[Evening]]
* [[Evertz Pharma]]
* [[Execution]]
* [[Exmouth, Western Australia]]
* [[Exploding Kittens]]
* [[Eyal Zamir]]
* [[FC Hertha Wels]]
* [[Family Jr.]]
* [[Father]]
* [[Favoritner AC]]
* [[Fawzia of Egypt]]
* [[Fazail-e-Amaal]]
* [[Fear & Hunger]]
* [[Ferdinand Popelka]]
* [[Fermentation]]
* [[Fianna]]
* [[Filchner–Ronne Ice Shelf]]
* [[Fiqh]]
* [[First-past-the-post voting]]
* [[Fitzroy Crossing]]
* [[Flag of Austria]]
* [[Flag of Brunei]]
* [[Flag of Croatia]]
* [[Flag of Dominica]]
* [[Flag of Iran]]
* [[Flag of Montenegro]]
* [[Flag of Slovakia]]
* [[Fontaine, Isère]]
* [[Ford Escape]]
* [[Forest Grove, Western Australia]]
* [[Forts in India]]
* [[Francia]]
* [[Francis Bacon (artist)]]
* [[Frankincense]]
* [[Frans Krätzig]]
* [[Franz Aigner]]
* [[Franz Erdl]]
* [[Franz Hanreiter]]
* [[Franz Hasil]]
* [[Franz Hofer]]
* [[Franz Kaspirek]]
* [[Franz Schiemer]]
* [[Franz Schilling]]
* [[Franz Swoboda]]
* [[Freedom 250]]
* [[Friday Plans, Inc.]]
* [[Friedrich Brandstetter]]
* [[Friedrich Wöhler]]
* [[Fuckin' in the Bushes]]
* [[Fuel cell vehicle]]
* [[Funk metal]]
* [[Future]]
* [[Gamma ray]]
* [[Gasterosteoidei]]
* [[Geffen Records]]
* [[Geometry Dash]]
* [[Georg Schors]]
* [[Georgy Zhukov]]
* [[Geraldton (suburb)]]
* [[German Antarctic Receiving Station]]
* [[German papiermark]]
* [[Getz Ice Shelf]]
* [[Ghaznavid Empire]]
* [[Gibson Desert]]
* [[Gillian Ayres]]
* [[Gingerbread Man (fairy tale)]]
* [[Giovanni Messe]]
* [[Glass Heart]]
* [[Glen Iris, Western Australia]]
* [[Glenfield, Western Australia]]
* [[Global Sumud Flotilla]]
* [[Gnarabup, Western Australia]]
* [[Gnowangerup]]
* [[Goddess]]
* [[Gongen]]
* [[Gospel of Thomas]]
* [[Government of France]]
* [[Government of India]]
* [[Government of Nazi Germany]]
* [[Government of Pakistan]]
* [[Government shutdowns in the United States]]
* [[Gracetown, Western Australia]]
* [[Grand Casino Arena]]
* [[Grays Harbor College]]
* [[Great Red Spot]]
* [[Great Saint James]]
* [[Great Wall Station]]
* [[Great hammerhead shark]]
* [[Greater London]]
* [[Greenbone]]
* [[Greenfinch]]
* [[Greenough, Western Australia]]
* [[Greenwich Island]]
* [[Group 14 element]]
* [[Group homomorphism]]
* [[Guacamole]]
* [[Guardians of the Galaxy (TV series)]]
* [[Guido von List]]
* [[Gulen, Norway]]
* [[Gustav Thaler]]
* [[Gwalia, Western Australia]]
* [[Gyrosteus]]
* [[Günther Neukirchner]]
* [[HELLO (BPM) 2026]]
* [[HPV vaccine]]
* [[Habermehl Rock]]
* [[Hairdresser]]
* [[Halden]]
* [[Half Angel (1936 movie)]]
* [[Halley Research Station]]
* [[Halls Creek]]
* [[Hamelin Bay, Western Australia]]
* [[Hampshire]]
* [[Hanafi]]
* [[Hanbali]]
* [[Hanjirō Asanuma]]
* [[Hans Krankl]]
* [[Happy Pot]]
* [[Harbin Z-9]]
* [[Hareid]]
* [[Harvard, Illinois]]
* [[Hawksbill sea turtle]]
* [[Hayaakitsuhiko and Hayaakitsuhime]]
* [[Hazara]]
* [[Health and appearance of Michael Jackson]]
* [[Heimdal]]
* [[Heimo Pfeifenberger]]
* [[Heinrich Hiltl]]
* [[Heinrich Müller (footballer)]]
* [[Heinrich Retschury]]
* [[Hemsedal]]
* [[Henri Paul]]
* [[Henryk Arctowski Polish Antarctic Station]]
* [[Herbert Gager]]
* [[Herbert Prohaska]]
* [[Herbert Rettensteiner]]
* [[Hereditary Sheriff of Fife]]
* [[Herfried Sabitzer]]
* [[Hermann Stadler]]
* [[Herring cale]]
* [[Hewlett-Packard]]
* [[Hexafluoride]]
* [[Hezi Rash]]
* [[Hikaru Utada]]
* [[Hill Dickinson Stadium]]
* [[Hill of Howth Tramway]]
* [[Hippocratic oath]]
* [[Hirokazu Yasuhara]]
* [[History of colonialism]]
* [[Hole, Norway]]
* [[HomePod]]
* [[Horae]]
* [[Horlivka]]
* [[Horten]]
* [[Hotel Transylvania: Hunting The End]]
* [[Huara]]
* [[Hugh O'Neill, Earl of Tyrone]]
* [[Hughenden, Queensland]]
* [[Human After All]]
* [[Hung Cao]]
* [[Huso]]
* [[Hyden, Western Australia]]
* [[Hypercholesterolemia]]
* [[I'm a Celebrity: Unpacked]]
* [[I'm a Celebrity…Get Me Out of Here! (British TV series 24)]]
* [[IPad Air (7th generation)]]
* [[Ibrahim Sekagya]]
* [[Ice cap climate]]
* [[Ilia Ivanschitz]]
* [[Illegal drugs]]
* [[Ilyushin Il-38]]
* [[Immigration]]
* [[Inch]]
* [[Indian Americans]]
* [[Indianapolis Boulevard]]
* [[Indianapolis International Airport]]
* [[India–North Korea relations]]
* [[Influencer]]
* [[Infoglobal I-22 Sikatan]]
* [[Injil]]
* [[Instinctools (company)]]
* [[Intercrural sex]]
* [[Internet forum]]
* [[Invincible (TV series)]]
* [[Iris (goddess)]]
* [[Isca Augusta]]
* [[Ishan Kishan]]
* [[Islam in Austria]]
* [[Islamic State – Syria Province]]
* [[Ismael "El Mayo" Zambada]]
* [[Isotope]]
* [[Israel Railways]]
* [[Italki]]
* [[Iğdır]]
* [[J3 League]]
* [[Ja'far]]
* [[Jacob Rasmussen]]
* [[Jakob Zangerl]]
* [[Jalisco New Generation Cartel]]
* [[Jamal Raisani]]
* [[James Packer]]
* [[Jang Bogo Station]]
* [[Jannik Schuuster]]
* [[January 2024]]
* [[Japanese diaspora]]
* [[Jarasandha]]
* [[Jayne Torvill]]
* [[Jazz fusion]]
* [[Jeonju Shrine]]
* [[Jess Mariano]]
* [[Jevnaker]]
* [[Jewish High Priest]]
* [[Jewish National Front]]
* [[Jigalong]]
* [[Jim Varney]]
* [[Jinshin War]]
* [[Joaquín Guzmán López]]
* [[Johann Klima]]
* [[John Chizoba Vincent]]
* [[John Roach]]
* [[John T. Myers]]
* [[John the Apostle]]
* [[Jokela school shooting]]
* [[Joker (The Dark Knight)]]
* [[Jonathan Ive]]
* [[Jonny Greenwood]]
* [[Jose Mari Chan]]
* [[Josef Haist]]
* [[Josef Hamerl]]
* [[Josef Hickersberger]]
* [[Joseph City, Arizona]]
* [[Julia Creek, Queensland]]
* [[Jundah, Queensland]]
* [[József Dunszt]]
* [[KSI vs. Logan Paul II]]
* [[Kailram]]
* [[Kakraita (village)]]
* [[Kaluga (sturgeon)]]
* [[Kalumburu]]
* [[Kapfenberg SV]]
* [[Karl Bortoli]]
* [[Karl Schott]]
* [[Karl Tekusch]]
* [[Karl Zankl]]
* [[Karloo, Western Australia]]
* [[Karpe]]
* [[Kashikuri Shrine]]
* [[Katanning]]
* [[Kate Spade New York]]
* [[Katja Wienerroither]]
* [[Kazimierz Ludwik Piłsudski]]
* [[Keke Palmer]]
* [[Kelly Scholz]]
* [[Kenne Fant]]
* [[Kenneth Branagh]]
* [[Kent Police]]
* [[Kenyaichthys]]
* [[Kerbal Space Program]]
* [[Kerim Alajbegovic]]
* [[Ketenylidene]]
* [[Kevin Federline]]
* [[Khalil Ahmad Saharanpuri]]
* [[Khanke]]
* [[Khongtekcha]]
* [[Khwarazmian invasion of Oman Gulf (1212-1215)]]
* [[Kichijōji]]
* [[Kigali]]
* [[Killing of Yasser Abu Shabab]]
* [[Kimberley (Western Australia)]]
* [[King George Island (Antarctica)]]
* [[King George V DLR station]]
* [[King Sejong Station]]
* [[Kingoonya]]
* [[Kingsauce]]
* [[Kinue Asanuma]]
* [[Kirby (character)]]
* [[Klepp]]
* [[Knights of the Round Table]]
* [[Knowledge graph]]
* [[Kofun]]
* [[Kojarena, Western Australia]]
* [[Kompira]]
* [[Korean Catholic Association]]
* [[Korean sandlance]]
* [[Kremser SC]]
* [[Kristin Harila]]
* [[Krødsherad]]
* [[Kudardup, Western Australia]]
* [[Kununurra, Western Australia]]
* [[Kuwohi]]
* [[LWVNH v. Kramer]]
* [[La Chapelle-Craonnaise]]
* [[La France insoumise]]
* [[La Tour-du-Pin]]
* [[Labriformes]]
* [[Lagrange, Western Australia]]
* [[Lalish]]
* [[Lambda calculus]]
* [[Lancashire]]
* [[Land Rover]]
* [[Landscape painting]]
* [[Lapstone, New South Wales]]
* [[Lassina Traoré (footballer, born 2007)]]
* [[Latin alphabet]]
* [[Laverton, Western Australia]]
* [[Le Griffon]]
* [[Leaf litter]]
* [[Leeuwin, Western Australia]]
* [[Leeuwin-Naturaliste National Park]]
* [[Leicestershire]]
* [[Leigh Creek, South Australia]]
* [[Lena Mukhina]]
* [[Lent]]
* [[Leonhard Machu]]
* [[Leonora, Western Australia]]
* [[Leopardus]]
* [[Leopold Giebisch]]
* [[Lepisosteiformes]]
* [[Lepisosteus]]
* [[Lesdain]]
* [[Lev Shomea]]
* [[Life Is Beautiful]]
* [[Light truck]]
* [[Lightning Ridge]]
* [[Like Phantoms, Forever]]
* [[Linear Pottery culture]]
* [[Lionel Shapiro]]
* [[List of Dexter's Laboratory episodes]]
* [[List of General Secretaries of the Soviet Union]]
* [[List of Shikinaisha in Shima Province]]
* [[List of barons and lords in Britain and Ireland today]]
* [[List of fugal works by Johann Sebastian Bach]]
* [[List of international goals scored by Sadio Mané]]
* [[List of international matches between Austria and Hungary]]
* [[List of mathematical symbols]]
* [[List of prehistoric bony fish genera]]
* [[List of search engines]]
* [[Little weed whiting]]
* [[Live at Wembley July 16, 1988]]
* [[Lombadina]]
* [[London Eye]]
* [[London Interdisciplinary School]]
* [[Longreach, Queensland]]
* [[Looma]]
* [[Loppa]]
* [[Los Pica Pica]]
* [[Luca Salsi]]
* [[Lucas Reiner]]
* [[Luce (mascot)]]
* [[Lugaid Mac Con]]
* [[Luka Sero]]
* [[Lyndhurst, South Australia]]
* [[Mac. Robertson Land]]
* [[Macquarie Island Station]]
* [[Magadhan Empire]]
* [[Magick]]
* [[Mahadzir Mohd Khir]]
* [[Mahomets Flats, Western Australia]]
* [[Mainland Japan]]
* [[Maitri (research station)]]
* [[Malagueta pepper]]
* [[Maleic anhydride]]
* [[Malik Asghar Ali Qaisar]]
* [[Man or bear]]
* [[Mangar (fish)]]
* [[Manjimup, Western Australia]]
* [[Mansudae]]
* [[Marble Bar]]
* [[Marc Platt (producer)]]
* [[Margaret Tudor]]
* [[Mario Party: The Top 100]]
* [[Mark Goulston]]
* [[Marker, Norway]]
* [[Martock]]
* [[Marwan Hadid]]
* [[Mary Ann Crowe]]
* [[Masala]]
* [[Mataranka, Northern Territory]]
* [[Mathematics education]]
* [[Mathieu Blondeau]]
* [[Matt Blumenthal]]
* [[Matt Katz-Bohen]]
* [[Mawlid]]
* [[Mayker Palacios]]
* [[Mazda CX-30]]
* [[Meeker, Oklahoma]]
* [[Meissa]]
* [[Merredin, Western Australia]]
* [[Meru, Western Australia]]
* [[Metriacanthosaurus]]
* [[Metricup, Western Australia]]
* [[Metropolitan Transportation Authority Police]]
* [[Michael Konsel]]
* [[Michele Ferrero]]
* [[Mid West (Western Australia)]]
* [[Midichlorian]]
* [[Midwest Political Science Association]]
* [[Mihashira Torii]]
* [[Mild intellectual disability]]
* [[Mildura]]
* [[Millennium Prize Problems]]
* [[Mindanao rasbora]]
* [[Minimum wage]]
* [[Minister of Sports]]
* [[Ministry of Education (China)]]
* [[Minnenooka, Western Australia]]
* [[Miracle Musical]]
* [[Mirim Horse Riding Club]]
* [[Mirza Mohammed Athar]]
* [[Mitsumine Shrine]]
* [[Mixed acid]]
* [[Modum]]
* [[Moe]]
* [[Mohammad Bagheri (general)]]
* [[Mohammad Pakpour]]
* [[Mohammed Rafi]]
* [[Molidae]]
* [[Molloy Island, Western Australia]]
* [[Moon wrasse]]
* [[Moresby, Western Australia]]
* [[Morphinan]]
* [[Moscow Center of SPARC Technologies (MCST)]]
* [[Moses Häusler]]
* [[Moss, Norway]]
* [[Mossad]]
* [[Mount Barker, Western Australia]]
* [[Mount Gambier]]
* [[Mount Magnet]]
* [[Mozambique Channel]]
* [[Mritunjay Kumar Tiwary]]
* [[Muhammad Ilyas Kandhlawi]]
* [[Muhammad Khudabanda]]
* [[Muhhadith]]
* [[Multiocular O]]
* [[Mungeranie, South Australia]]
* [[NA-185 Dera Ghazi Khan-II]]
* [[NGC 3521]]
* [[Naguib Sawiris]]
* [[Nancy Ajram]]
* [[Nannup, Western Australia]]
* [[Naomi Seibt]]
* [[Narvik]]
* [[Nasser El Sonbaty]]
* [[National Day Against Homophobia]]
* [[National flower of Uruguay]]
* [[Nelumbo lutea]]
* [[Neo-Mu'tazilites]]
* [[Neopentane]]
* [[Nesbyen]]
* [[Neumayer-III Station]]
* [[Nevis]]
* [[New World Order]]
* [[New Zealand English]]
* [[New shekel]]
* [[New vision]]
* [[Newhall estate]]
* [[Nickel Boys]]
* [[Niederanven]]
* [[Nillup, Western Australia]]
* [[Ninjapromo]]
* [[No quarter]]
* [[Noah Z. Jones]]
* [[Nobel Prize in Chemistry]]
* [[Nokia 5510]]
* [[Nonthaburi province]]
* [[Norbert Katz]]
* [[Nordkapp]]
* [[Nordre Land]]
* [[Nore og Uvdal]]
* [[Norseman, Western Australia]]
* [[North Yorkshire]]
* [[Northampton, Western Australia]]
* [[Notodden]]
* [[Nullagine]]
* [[Number Ones (video)]]
* [[Obaidullah Shadikhel]]
* [[Ocean sunfish]]
* [[Octagonal Kofun]]
* [[Odax]]
* [[Office for the Coordination of Humanitarian Affairs]]
* [[Oklahoma House of Representatives]]
* [[Omori (video game)]]
* [[Onion ring]]
* [[Onium ion]]
* [[Onslow, Western Australia]]
* [[Opel Vectra]]
* [[Oppo F17 Pro]]
* [[Organization of Iranian Kurdistan Struggle]]
* [[Orlando Bloom]]
* [[Orly]]
* [[Ornacieux]]
* [[Osmington, Western Australia]]
* [[Ottoman-Persian Wars]]
* [[Ottoman-Qajar War (1906-1907)]]
* [[Our Lady of Guadalupe]]
* [[Oxfordshire]]
* [[PICO-8]]
* [[Paintbrush]]
* [[Pallid sturgeon]]
* [[Palmer Station]]
* [[Panhandle, Texas]]
* [[Pannawonica]]
* [[Paramabhattaraka]]
* [[Paramount Pictures]]
* [[Paris-East Créteil University]]
* [[Parliament of South Australia]]
* [[Pat Nixon]]
* [[Patriots.eu]]
* [[Paul the Apostle]]
* [[Pear Deck]]
* [[Pelican Point, Western Australia]]
* [[Peloponnese]]
* [[Pemberton, Western Australia]]
* [[Pensacola Mountains]]
* [[Peppa Pig series 3]]
* [[Pepperjack cheese]]
* [[Pereslavl-Zalessky]]
* [[Periodic table]]
* [[Persepolis]]
* [[Peter Paul Rubens]]
* [[Peter Schöttel]]
* [[Pickleball]]
* [[Pierre Aubert]]
* [[Pine Creek, Northern Territory]]
* [[Plan de Rescate de Matemáticas]]
* [[Planettoon Tv]]
* [[Platonic solid]]
* [[Plug-in hybrid]]
* [[Point Samson]]
* [[Pokémon Pokopia]]
* [[Political positions of Robert F. Kennedy Jr.]]
* [[Polycyclic compound]]
* [[Polymerization]]
* [[Poon Hiu-wing]]
* [[Popular Forces]]
* [[Power cut]]
* [[Power factor]]
* [[Powerade]]
* [[Praça Soares Madruga]]
* [[President of Honduras]]
* [[Pressure suit]]
* [[Prevelly, Western Australia]]
* [[Profesor Julio Escudero Base]]
* [[Protarchaeopteryx]]
* [[Protest]]
* [[Protura]]
* [[Pseudoliparis swirei]]
* [[Puisseguin]]
* [[Puppy Bowl]]
* [[Puremba]]
* [[Pyongyang International Airport]]
* [[Quarter tone]]
* [[Quettabyte]]
* [[Quiñonería]]
* [[Qumran]]
* [[R62 (New York City Subway car)]]
* [[RDR2 Locomotives]]
* [[ROM hacking]]
* [[Race with Ryan]]
* [[Radu Theodoru]]
* [[Rahma Riad]]
* [[Rainbow cale]]
* [[Raje]]
* [[Rao Bika]]
* [[Rao Sahab]]
* [[Rasbora]]
* [[Razor]]
* [[Rebel News]]
* [[Redgate, Western Australia]]
* [[Reed pen]]
* [[Rekaz]]
* [[Relayball]]
* [[Repository (data)]]
* [[Resignation of Prince Andrew, Duke of York]]
* [[Reza Pahlavi, Crown Prince of Iran]]
* [[Rhinocerotoid]]
* [[Richmond, Queensland]]
* [[Ridge A]]
* [[Riemannian geometry]]
* [[Risala]]
* [[Robert Frind]]
* [[Robert Körner]]
* [[Robert Pavlicek]]
* [[Robert Sara]]
* [[Roberto Canessa]]
* [[Roberto Rossellini]]
* [[Robotic construction kit]]
* [[Rocco Zikovic]]
* [[Roeburne, Western Australia]]
* [[Rogers, Texas]]
* [[Roiffieux]]
* [[Rollag]]
* [[Rosa Brook, Western Australia]]
* [[Rosa Glen, Western Australia]]
* [[Ross Ice Shelf]]
* [[Round Kofun]]
* [[Royal Challenger Bangalore]]
* [[Rudi Hiden]]
* [[Rudolf Schlauf]]
* [[Rudolf Szanwald]]
* [[Rumble Fighter]]
* [[Runes]]
* [[Russian Republic]]
* [[Rutland]]
* [[Ryan Walters]]
* [[Réti Opening]]
* [[Rüstü Erdogan]]
* [[S. Shamsuddin]]
* [[SANAE IV]]
* [[SAS Viya]]
* [[SC Neusiedl am See]]
* [[SEAT Ibiza]]
* [[SK Vorwärts Steyr]]
* [[SV Gloggnitz]]
* [[Saint-Aubin-Fosse-Louvain]]
* [[Saint-Maurice-de-Beynost]]
* [[Saitama Seibu Lions]]
* [[Salon-de-Provence]]
* [[Sam Thompson]]
* [[Samsung Galaxy S series]]
* [[Samuel Gompers]]
* [[Samuel Lister, 1st Baron Masham]]
* [[San Pedro Claver church]]
* [[San Salvador]]
* [[Sandefjord]]
* [[Sandnes]]
* [[Sanel Kuljić]]
* [[Sarah Paine]]
* [[Saturn Outlook]]
* [[School year]]
* [[Schroeder, Western Australia]]
* [[Scott Base]]
* [[Scott River East, Western Australia]]
* [[Scott River, Western Australia]]
* [[Scottish clan]]
* [[Scramble for Africa]]
* [[Second Link Expressway]]
* [[Sero Brothers]]
* [[Sex therapy]]
* [[Shackleton Ice Shelf]]
* [[Shaggs' Own Thing]]
* [[Shaman]]
* [[Shangani Patrol]]
* [[Shiba Inu]]
* [[Shinto honorifics]]
* [[Shire of Augusta-Margaret River]]
* [[Shorts]]
* [[Shropshire]]
* [[Siege of Kinsale]]
* [[Sigdal]]
* [[Silver]]
* [[Silver bromide]]
* [[Silver cyanate]]
* [[Silverton, New South Wales]]
* [[Sinn Féin]]
* [[Sky Diver]]
* [[Sleeping Dogs]]
* [[Social Democratic Party (UK, 1990)]]
* [[Sogndal]]
* [[Soju]]
* [[Solanki Rajputs]]
* [[Somerset]]
* [[Soraya Esfandiary-Bakhtiary]]
* [[Sota Kitano]]
* [[Sour sugar]]
* [[South Bunbury, Western Australia]]
* [[South Burlington, Vermont]]
* [[South Geomagnetic Pole]]
* [[South Lebanon conflict (1985–2000)]]
* [[South Orkney Islands]]
* [[South Shetland Islands]]
* [[Southern lapwing]]
* [[Soviet Civil Administration]]
* [[Spanaway Junior High School shooting]]
* [[Square Kofun]]
* [[Staffordshire]]
* [[Standing Council of Scottish Chiefs]]
* [[Stavanger]]
* [[Stealth aircraft]]
* [[Stefan Skoumal]]
* [[Stefan Wagner]]
* [[Stefania Rivi]]
* [[Stereographic projection]]
* [[Steven V. Maksin]]
* [[Stjørdal]]
* [[Stoke Lucero]]
* [[Strong Bad's Cool Game for Attractive People]]
* [[Stygimoloch]]
* [[Sula, Møre og Romsdal]]
* [[Sulfoxide]]
* [[Sunella]]
* [[Sunnah]]
* [[Sunndal]]
* [[Super Animal Royale]]
* [[Super Mario Galaxy]]
* [[Suroz]]
* [[Surrey]]
* [[Susan Monarez]]
* [[Suzzane Cryer]]
* [[Svetlana Kuznetsova]]
* [[Syr Darya sturgeon]]
* [[São Paulo Metro]]
* [[São Paulo/Guarulhos International Airport]]
* [[Ségolène Royal]]
* [[Søndre Land]]
* [[TI-83 series]]
* [[TIC-80]]
* [[Tachycardia]]
* [[Tainrakuasuchus bellator]]
* [[Takashi Iizuka]]
* [[Takifugu]]
* [[Tamil Nadu]]
* [[Tampongate]]
* [[Tangkap Najib rally]]
* [[Tanto]]
* [[Taobao]]
* [[Tarzan (1999 movie)]]
* [[Tatsumi Kimishima]]
* [[Taurus (constellation)]]
* [[Te Wahipounamu]]
* [[Television network]]
* [[Telltale Incorporated]]
* [[Temple Terrace, Florida]]
* [[Teriberka]]
* [[Terra Nova Bay]]
* [[Territorial integrity]]
* [[Terry stop]]
* [[Tessellation]]
* [[Testaments of the Twelve Patriarchs]]
* [[The Ashes]]
* [[The Boy in the Striped Pyjamas]]
* [[The Brink's Job]]
* [[The Burren]]
* [[The Cat in the Hat (movie)]]
* [[The Charlie Brown and Snoopy Show]]
* [[The Dead Dance]]
* [[The Escapists]]
* [[The First Epistle of Clement to the Corinthians]]
* [[The Happy Lovers]]
* [[The Kleptocrats]]
* [[The Matrix Resurrections]]
* [[The Right Excellent]]
* [[The Taking of Pelham 123 (2009 movie)]]
* [[The Tree of Life (movie)]]
* [[The Twigs]]
* [[The Wordies]]
* [[Theodore Tugboat]]
* [[Thimphu]]
* [[Thirsk]]
* [[Thomas Starzl]]
* [[Thorn (letter)]]
* [[Three Munakata goddesses]]
* [[Three-spined stickleback]]
* [[Tilpa]]
* [[Tim Stokely]]
* [[Tincinae]]
* [[Titan triggerfish]]
* [[Titin]]
* [[Tom Price, Western Australia]]
* [[Tonpa Shenrab Miwoche]]
* [[Tony Gonzalez]]
* [[Tony Robbins]]
* [[Toretsk]]
* [[Toronto Argonauts]]
* [[Torre Rise]]
* [[Tory Green]]
* [[Toyōke Ōmikami]]
* [[Traditionalist theology (Islam)]]
* [[Tree (data structure)]]
* [[Treeton, Western Australia]]
* [[Trent Boult]]
* [[Triggerfish]]
* [[Triple Entente]]
* [[Tsukinami-no-Matsuri]]
* [[Tubemouth]]
* [[Tujunga Wash]]
* [[Tulsa King]]
* [[Tupolev Tu-124]]
* [[Tur Abdin]]
* [[Turkish war crimes]]
* [[Turoyo language]]
* [[Tyndall effect]]
* [[Tyne and Wear]]
* [[UEFA Women's Euro 2013]]
* [[USA (disambiguation)]]
* [[Umme Hassan]]
* [[Union Gurten]]
* [[Union Kleinmünchen]]
* [[United Nations Environment Programme]]
* [[United Parcel Service]]
* [[United States Associate Attorney General]]
* [[United States Department of Health and Human Services]]
* [[United States Department of Homeland Security]]
* [[United States Department of Justice]]
* [[United States Department of State]]
* [[United States Department of the Air Force]]
* [[United States Department of the Navy]]
* [[United States Deputy Attorney General]]
* [[United States Under Secretary of the Navy]]
* [[United States invasion of Grenada]]
* [[Universidade Regional do Cariri]]
* [[University of Indonesia]]
* [[University of Nevada, Las Vegas]]
* [[Unzen Onsen Shrine]]
* [[Urban area]]
* [[Usher, Western Australia]]
* [[Uttar Pradesh]]
* [[VTR (Chile)]]
* [[Vagabonds!]]
* [[Valea Moldovei]]
* [[Van, Turkey]]
* [[Vasishthiputra Vasusena]]
* [[Vestre Toten]]
* [[Vestvågøy]]
* [[Victoria Land]]
* [[Vietnam National University, Hanoi]]
* [[Vietnamese food]]
* [[Villa Las Estrellas]]
* [[Vimba elongata]]
* [[Vinje]]
* [[Vinson Elkins]]
* [[Vinyl norbornene]]
* [[Violence against men]]
* [[Violetta Shostak]]
* [[Visionary: The Video Singles]]
* [[Vistula–Oder offensive]]
* [[Vittoria, Western Australia]]
* [[Volda]]
* [[Volkswagen T-Cross]]
* [[Volkswagen T-Roc]]
* [[Volkswagen Taigo]]
* [[Volvo XC40]]
* [[Walter Kogler]]
* [[Walter Skocik]]
* [[Wanaaring, New South Wales]]
* [[Warmun Community, Western Australia]]
* [[Warner Glen, Western Australia]]
* [[Warwickshire]]
* [[We (Cyrillic)]]
* [[Weaponization of antisemitism]]
* [[Web browser]]
* [[Wedding of Prince Charles and Lady Diana Spencer]]
* [[Wedgie]]
* [[Welfare state]]
* [[Welsh Football League]]
* [[Wenzhou]]
* [[West Antarctic Ice Sheet]]
* [[West Antarctica]]
* [[West Midlands (county)]]
* [[White Cliffs, New South Wales]]
* [[Wickham, Western Australia]]
* [[Wiener Sport-Club]]
* [[Wilcannia]]
* [[Wilkes Land]]
* [[William Creek, South Australia]]
* [[Wiltshire]]
* [[Windorah]]
* [[Winifred Atwell]]
* [[Winton, Queensland]]
* [[Witchcliffe, Western Australia]]
* [[Withers, Western Australia]]
* [[Wolfgang Mair]]
* [[Woomera, South Australia]]
* [[Worcestershire]]
* [[Wyndham, Western Australia]]
* [[X&Y (album)]]
* [[Xinhua Bookstore]]
* [[YSCC Yokohama]]
* [[Yachad (political party)]]
* [[Yadav]]
* [[Yamato Ōkunitama Shrine]]
* [[Yasoob Abbas]]
* [[Yeat]]
* [[Yebble, Western Australia]]
* [[Yumna Marwan]]
* [[Yungngora]]
* [[Yunus Jaunpuri]]
* [[Yuri (manga)]]
* [[Zabeel Stadium]]
* [[Zakariyya Kandhlawi]]
* [[Zezé Procópio]]
* [[Zinzi Coogler]]
* [[Zlatoust]]
* [[Zucchelli Station]]
* [[Zune Software]]
* [[Émilienne Malfatto]]
* [[Évreux]]
* [[Øvre Eiker]]
* [[Ōharae no Kotoba]]
* [[Ōmiya-no-Me]]
* [[Śūnyatā]]
* [[Şemdinli]]
j9ed9kruiurvpfmvrdgh10lkm3z3yrk
737272
737270
2026-04-08T20:15:45Z
Asteralee
69542
section
737272
wikitext
text/x-wiki
==Reference list missing==
* [[1-Propanol]]
* [[1956-57 Austrian football championship]]
* [[1957-58 Austrian football championship]]
* [[1958-59 Austrian football championship]]
* [[1959-60 Austrian football championship]]
* [[1960-61 Austrian football championship]]
* [[1961-62 Austrian football championship]]
* [[1962-63 Austrian football championship]]
* [[1963-64 Austrian football championship]]
* [[1966-67 Austrian football championship]]
* [[1967-68 Austrian football championship]]
* [[1969-70 Austrian football championship]]
* [[1971-72 Austrian football championship]]
* [[1972-73 Austrian football championship]]
* [[1973-74 Austrian football championship]]
* [[1974–75 Austrian Cup]]
* [[1979-80 Austrian football championship]]
* [[1980-81 Austrian football championship]]
* [[1981-82 Austrian football championship]]
* [[1982-83 Austrian football championship]]
* [[1983-84 Austrian football championship]]
* [[1984-85 Austrian football championship]]
* [[1985- 86 Austrian football championship]]
* [[1986-87 Austrian football championship]]
* [[1987-88 Austrian football championship]]
* [[1988-89 Austrian football championship]]
* [[1989-90 Austrian football championship]]
* [[1990-91 Austrian football championship]]
* [[1991-92 Austrian football championship]]
* [[1992-93 Austrian football championship]]
* [[1995-96 Austrian football championship]]
* [[1997-98 Austrian football championship]]
* [[1998-99 Austrian football championship]]
* [[1999- 00 Austrian football championship]]
* [[2,2,4-Trimethylpentane]]
* [[2008 baby milk scandal]]
* [[2023 Miami Dolphins season]]
* [[2024 Rafah ambush]]
* [[2024–2025 Canada Post labour dispute]]
* [[2025 FIFA U-17 World Cup]]
* [[2026 SDF missile strikes in Hasakah]]
* [[2C-B]]
* [[3I/ATLAS]]
* [[46 (number)]]
* [[AICEL Shura Hall]]
* [[ANO (political party)]]
* [[Abatacept]]
* [[Abbas Araghchi]]
* [[Abdur Rahman ibn Yusuf Mangera]]
* [[Aboubacar Camara]]
* [[Acton Park, Western Australia]]
* [[Africa Movie Academy Awards]]
* [[Ahmad Vahidi]]
* [[Air Buddies]]
* [[Alessandro Rinaldi]]
* [[Alex Coco]]
* [[Alexander Popovich]]
* [[Alexandra Bridge, Western Australia]]
* [[Alexandra Eala]]
* [[Alfred Hörtnagl]]
* [[Alparslan Baran]]
* [[Alto Pass, Illinois]]
* [[Amatsukami]]
* [[Ambergate, Western Australia]]
* [[American League West]]
* [[Amia (fish)]]
* [[Aminul Islam Bulbul]]
* [[Amir Hatami]]
* [[Amnesia]]
* [[Andamooka]]
* [[Andreas Ogris]]
* [[Angélica Rivera]]
* [[Anniebrook, Western Australia]]
* [[Anrie Chase]]
* [[Ansan]]
* [[Antarctic Plateau]]
* [[Antigonid dynasty]]
* [[Anton Fritsch]]
* [[Anton Wegscheider]]
* [[AppBlock]]
* [[Arcadia 2001]]
* [[Ardyaloon]]
* [[Argyrosomus]]
* [[Argyrosomus japonicus]]
* [[Argyrosomus regius]]
* [[Artigas Base]]
* [[Asgar Ali Qaisar]]
* [[Ashish Vijay Kumar]]
* [[Assyrian rebellion]]
* [[Aston Martin in Formula One]]
* [[Astra Tech]]
* [[Atalanta]]
* [[Atomic radius]]
* [[Atractosteus]]
* [[Attacks on civilians in the Russian invasion of Ukraine]]
* [[Attorney General's Office (United Kingdom)]]
* [[Attorney general]]
* [[Attraction to transgender people]]
* [[Au Sable River (Michigan)]]
* [[Au Sable Township, Iosco County, Michigan]]
* [[Au Sable Township, Roscommon County, Michigan]]
* [[Audi A3]]
* [[Audi A6]]
* [[Audi Q6 e-tron]]
* [[August Blaha]]
* [[August Kraupar]]
* [[August Revolution]]
* [[Augusta, Western Australia]]
* [[Aulorhynchus]]
* [[Australind, Western Australia]]
* [[Australosomus]]
* [[Auto-B-Good]]
* [[Aziz Nasirzadeh]]
* [[Babel (movie)]]
* [[Backergunje]]
* [[Bad (tour)]]
* [[Bad 25]]
* [[Bagdad, Kentucky]]
* [[Bahadur Shah II]]
* [[Bahishti Zewar]]
* [[Bakassi conflict]]
* [[Balignicourt]]
* [[Balistes]]
* [[Balistes vetula]]
* [[Balistoides]]
* [[Balistomorphus]]
* [[Ballota]]
* [[Balnot-sur-Laignes]]
* [[Barcaldine, Queensland]]
* [[Baron of Irrus]]
* [[Base General Bernardo O'Higgins Riquelme]]
* [[Bathylychnops exilis]]
* [[Battle of Dograi]]
* [[Baudin, Western Australia]]
* [[Beachlands, Western Australia]]
* [[Beagle Bay]]
* [[Beasts of the Southern Wild]]
* [[Beirut Nights]]
* [[Beisfjord Bridge]]
* [[Bell MTS]]
* [[Bellingshausen Station]]
* [[Beluga (sturgeon)]]
* [[Berkeley Software Distribution]]
* [[Bermuda Weather Service]]
* [[Bichir]]
* [[Bidyadanga Community]]
* [[Big Mamma]]
* [[Black widow pulsar]]
* [[Blackall]]
* [[Blue weed whiting]]
* [[Bluff Point, Western Australia]]
* [[Bob Kane]]
* [[Bobby Helms]]
* [[Bobrisky]]
* [[Bohuslav]]
* [[Boranup, Western Australia]]
* [[Boreas]]
* [[Boundless Immigration]]
* [[Boxer (dog)]]
* [[Brady Corbet]]
* [[Bramley, Western Australia]]
* [[Brass instrument]]
* [[Breath sounds]]
* [[Brest, France]]
* [[Bridgetown, Western Australia]]
* [[Briton languages]]
* [[Brook stickleback]]
* [[Brown-headed cowbird]]
* [[Bruce Arena]]
* [[Brunt Ice Shelf]]
* [[Bugatti]]
* [[Bunbury, Western Australia (suburb)]]
* [[Bunrei]]
* [[Burnside, Western Australia]]
* [[Burt Lancaster]]
* [[Bus]]
* [[Buttonquail]]
* [[CBS Radio]]
* [[CP Lee]]
* [[Camarones, Chile]]
* [[Cambridgeshire]]
* [[Cameron Ocasio]]
* [[Camooweal]]
* [[Cape Burney, Western Australia]]
* [[Captain Arturo Prat Base]]
* [[Carbon tetrafluoride]]
* [[Carey Park, Western Australia]]
* [[Carlini Base]]
* [[Carolin Größinger]]
* [[Casino Royale (book)]]
* [[Catalysis]]
* [[Catherine II of Russia]]
* [[Cation]]
* [[CentOS]]
* [[Cerebellum]]
* [[Chaika (boat)]]
* [[Chalcopyrite]]
* [[Chamba district]]
* [[Chanodichthys]]
* [[Charles Brook, Newfoundland and Labrador]]
* [[Charleville, Queensland]]
* [[Chatte]]
* [[Chemical vapor deposition]]
* [[Cheshire]]
* [[Chifley Tower]]
* [[China Airlines Flight 676]]
* [[China National Space Administration]]
* [[Chingthang Lanthaba]]
* [[Chino, California]]
* [[Chondrosteidae]]
* [[Chopper (movie)]]
* [[Chris Meledandri]]
* [[Christian Kraiger]]
* [[Christopher Dean]]
* [[Christopher Street Day]]
* [[Chun Kuk Do]]
* [[Cimolichthys]]
* [[City of Bunbury]]
* [[City of Busselton]]
* [[City of Greater Geraldton]]
* [[Clan Borthwick]]
* [[Clan O'Donnell]]
* [[Clan Riddell]]
* [[Clarence, Tasmania]]
* [[Clement Bischoff]]
* [[Clinton body count conspiracy theory]]
* [[Clock]]
* [[Clockwork radio]]
* [[Cloncurry, Queensland]]
* [[Clown triggerfish]]
* [[Coats Land]]
* [[Cobitidae]]
* [[Cockatiel]]
* [[College Grove, Western Australia]]
* [[Colonialism]]
* [[Colorado Springs nightclub shooting]]
* [[Comandante Ferraz Antarctic Station]]
* [[Commonwealth realm]]
* [[Communist Party of Bangladesh]]
* [[Comparative Method (in Anthropology)]]
* [[Computational complexity theory]]
* [[Computer forensics]]
* [[Conium]]
* [[Constructed language]]
* [[Cook Out (restaurant)]]
* [[Coolgardie, Western Australia]]
* [[Cooperation]]
* [[Court of public opinion]]
* [[Courtenay, Western Australia]]
* [[Covalent bond]]
* [[Cowaramup, Western Australia]]
* [[Cox's Orange Pippin]]
* [[Crab pulsar]]
* [[Cristhian Bahena Rivera]]
* [[Cross stitch]]
* [[Crowdfunding]]
* [[Crucible]]
* [[Crystal Kay]]
* [[Culter]]
* [[Cunnamulla]]
* [[Cyprinus yilongensis]]
* [[DALL-E]]
* [[DDG (rapper)]]
* [[DFC Prag]]
* [[DJ Akademiks]]
* [[DVD player]]
* [[Daedalichthys]]
* [[Daly Waters]]
* [[Dampier, Western Australia]]
* [[Dangerous: The Short Films]]
* [[Dario Moretti]]
* [[Darko Todorovic]]
* [[Davenport, Western Australia]]
* [[Death Race (2008 movie)]]
* [[Death of Zara Qairina Mahathir]]
* [[Deaths in 1945]]
* [[Deaths in May 2025]]
* [[Deepdale, Western Australia]]
* [[Deepdene, Western Australia]]
* [[Deities of seventeen generations]]
* [[Democratic Autonomous Administration of North and East Syria]]
* [[Democratic backsliding]]
* [[Den Harrow]]
* [[Denmark, Western Australia]]
* [[Dennō Senshi Porygon]]
* [[Derby, Western Australia]]
* [[Deuterocanonical books]]
* [[Dhaka University of Engineering & Technology, Gazipur]]
* [[DiDuLa]]
* [[Diana Ross Presents The Jackson 5]]
* [[Dimethyldichlorosilane]]
* [[Dirac equation]]
* [[Dirk Nannes]]
* [[Disney Television Animation]]
* [[Division by zero]]
* [[Djarindjin]]
* [[Dodge Ram]]
* [[Dome A]]
* [[Dome C]]
* [[Dome F]]
* [[Donda 2]]
* [[Donkey Kong Bananza]]
* [[Donnybrook, Western Australia]]
* [[Doping in the United States]]
* [[Doraemon: Nobita to Fukkatsu no Hoshi]]
* [[Doric dialect (Scotland)]]
* [[Dorset]]
* [[Douarnenez]]
* [[Drummond Cove, Western Australia]]
* [[Dunsborough, Western Australia]]
* [[Durga]]
* [[Dux Express]]
* [[EUR-ACE label]]
* [[Earl of Rothes]]
* [[East Antarctic Ice Sheet]]
* [[East Augusta, Western Australia]]
* [[East Bunbury, Western Australia]]
* [[East Riding of Yorkshire]]
* [[Easter egg (video games)]]
* [[Eaux-Bonnes]]
* [[Ebenaqua]]
* [[Ebo Noah]]
* [[Eddie Bauer]]
* [[Eddie Hearn]]
* [[Eddie Shore]]
* [[Eduard Bauer]]
* [[Effect]]
* [[Eid al-Adha]]
* [[Electroencephalography]]
* [[Electronic waste]]
* [[Elizabeth Kelly]]
* [[Ellsworth Land]]
* [[Ellsworth Mountains]]
* [[Eloise Yung Shih King]]
* [[Elín Elmarsdóttir Van Pelt]]
* [[Emerald Fennell]]
* [[Emperor Kōbun]]
* [[Engelbert Smutny]]
* [[Enrique Collar]]
* [[Entropy]]
* [[Epinephelus]]
* [[Equilibrium]]
* [[Eradu, Western Australia]]
* [[Erich Probst]]
* [[Ernst Kozlicek]]
* [[Ernö Schwarz]]
* [[Erotic literature]]
* [[Eschmeyer nexus]]
* [[Esperanza Base]]
* [[Essex]]
* [[Euthynotus]]
* [[Evening]]
* [[Evertz Pharma]]
* [[Execution]]
* [[Exmouth, Western Australia]]
* [[Exploding Kittens]]
* [[Eyal Zamir]]
* [[FC Hertha Wels]]
* [[Family Jr.]]
* [[Father]]
* [[Favoritner AC]]
* [[Fawzia of Egypt]]
* [[Fazail-e-Amaal]]
* [[Fear & Hunger]]
* [[Ferdinand Popelka]]
* [[Fermentation]]
* [[Fianna]]
* [[Filchner–Ronne Ice Shelf]]
* [[Fiqh]]
* [[First-past-the-post voting]]
* [[Fitzroy Crossing]]
* [[Flag of Austria]]
* [[Flag of Brunei]]
* [[Flag of Croatia]]
* [[Flag of Dominica]]
* [[Flag of Iran]]
* [[Flag of Montenegro]]
* [[Flag of Slovakia]]
* [[Fontaine, Isère]]
* [[Ford Escape]]
* [[Forest Grove, Western Australia]]
* [[Forts in India]]
* [[Francia]]
* [[Francis Bacon (artist)]]
* [[Frankincense]]
* [[Frans Krätzig]]
* [[Franz Aigner]]
* [[Franz Erdl]]
* [[Franz Hanreiter]]
* [[Franz Hasil]]
* [[Franz Hofer]]
* [[Franz Kaspirek]]
* [[Franz Schiemer]]
* [[Franz Schilling]]
* [[Franz Swoboda]]
* [[Freedom 250]]
* [[Friday Plans, Inc.]]
* [[Friedrich Brandstetter]]
* [[Friedrich Wöhler]]
* [[Fuckin' in the Bushes]]
* [[Fuel cell vehicle]]
* [[Funk metal]]
* [[Future]]
* [[Gamma ray]]
* [[Gasterosteoidei]]
* [[Geffen Records]]
* [[Geometry Dash]]
* [[Georg Schors]]
* [[Georgy Zhukov]]
* [[Geraldton (suburb)]]
* [[German Antarctic Receiving Station]]
* [[German papiermark]]
* [[Getz Ice Shelf]]
* [[Ghaznavid Empire]]
* [[Gibson Desert]]
* [[Gillian Ayres]]
* [[Gingerbread Man (fairy tale)]]
* [[Giovanni Messe]]
* [[Glass Heart]]
* [[Glen Iris, Western Australia]]
* [[Glenfield, Western Australia]]
* [[Global Sumud Flotilla]]
* [[Gnarabup, Western Australia]]
* [[Gnowangerup]]
* [[Goddess]]
* [[Gongen]]
* [[Gospel of Thomas]]
* [[Government of France]]
* [[Government of India]]
* [[Government of Nazi Germany]]
* [[Government of Pakistan]]
* [[Government shutdowns in the United States]]
* [[Gracetown, Western Australia]]
* [[Grand Casino Arena]]
* [[Grays Harbor College]]
* [[Great Red Spot]]
* [[Great Saint James]]
* [[Great Wall Station]]
* [[Great hammerhead shark]]
* [[Greater London]]
* [[Greenbone]]
* [[Greenfinch]]
* [[Greenough, Western Australia]]
* [[Greenwich Island]]
* [[Group 14 element]]
* [[Group homomorphism]]
* [[Guacamole]]
* [[Guardians of the Galaxy (TV series)]]
* [[Guido von List]]
* [[Gulen, Norway]]
* [[Gustav Thaler]]
* [[Gwalia, Western Australia]]
* [[Gyrosteus]]
* [[Günther Neukirchner]]
* [[HELLO (BPM) 2026]]
* [[HPV vaccine]]
* [[Habermehl Rock]]
* [[Hairdresser]]
* [[Halden]]
* [[Half Angel (1936 movie)]]
* [[Halley Research Station]]
* [[Halls Creek]]
* [[Hamelin Bay, Western Australia]]
* [[Hampshire]]
* [[Hanafi]]
* [[Hanbali]]
* [[Hanjirō Asanuma]]
* [[Hans Krankl]]
* [[Happy Pot]]
* [[Harbin Z-9]]
* [[Hareid]]
* [[Harvard, Illinois]]
* [[Hawksbill sea turtle]]
* [[Hayaakitsuhiko and Hayaakitsuhime]]
* [[Hazara]]
* [[Health and appearance of Michael Jackson]]
* [[Heimdal]]
* [[Heimo Pfeifenberger]]
* [[Heinrich Hiltl]]
* [[Heinrich Müller (footballer)]]
* [[Heinrich Retschury]]
* [[Hemsedal]]
* [[Henri Paul]]
* [[Henryk Arctowski Polish Antarctic Station]]
* [[Herbert Gager]]
* [[Herbert Prohaska]]
* [[Herbert Rettensteiner]]
* [[Hereditary Sheriff of Fife]]
* [[Herfried Sabitzer]]
* [[Hermann Stadler]]
* [[Herring cale]]
* [[Hewlett-Packard]]
* [[Hexafluoride]]
* [[Hezi Rash]]
* [[Hikaru Utada]]
* [[Hill Dickinson Stadium]]
* [[Hill of Howth Tramway]]
* [[Hippocratic oath]]
* [[Hirokazu Yasuhara]]
* [[History of colonialism]]
* [[Hole, Norway]]
* [[HomePod]]
* [[Horae]]
* [[Horlivka]]
* [[Horten]]
* [[Hotel Transylvania: Hunting The End]]
* [[Huara]]
* [[Hugh O'Neill, Earl of Tyrone]]
* [[Hughenden, Queensland]]
* [[Human After All]]
* [[Hung Cao]]
* [[Huso]]
* [[Hyden, Western Australia]]
* [[Hypercholesterolemia]]
* [[I'm a Celebrity: Unpacked]]
* [[I'm a Celebrity…Get Me Out of Here! (British TV series 24)]]
* [[IPad Air (7th generation)]]
* [[Ibrahim Sekagya]]
* [[Ice cap climate]]
* [[Ilia Ivanschitz]]
* [[Illegal drugs]]
* [[Ilyushin Il-38]]
* [[Immigration]]
* [[Inch]]
* [[Indian Americans]]
* [[Indianapolis Boulevard]]
* [[Indianapolis International Airport]]
* [[India–North Korea relations]]
* [[Influencer]]
* [[Infoglobal I-22 Sikatan]]
* [[Injil]]
* [[Instinctools (company)]]
* [[Intercrural sex]]
* [[Internet forum]]
* [[Invincible (TV series)]]
* [[Iris (goddess)]]
* [[Isca Augusta]]
* [[Ishan Kishan]]
* [[Islam in Austria]]
* [[Islamic State – Syria Province]]
* [[Ismael "El Mayo" Zambada]]
* [[Isotope]]
* [[Israel Railways]]
* [[Italki]]
* [[Iğdır]]
* [[J3 League]]
* [[Ja'far]]
* [[Jacob Rasmussen]]
* [[Jakob Zangerl]]
* [[Jalisco New Generation Cartel]]
* [[Jamal Raisani]]
* [[James Packer]]
* [[Jang Bogo Station]]
* [[Jannik Schuuster]]
* [[January 2024]]
* [[Japanese diaspora]]
* [[Jarasandha]]
* [[Jayne Torvill]]
* [[Jazz fusion]]
* [[Jeonju Shrine]]
* [[Jess Mariano]]
* [[Jevnaker]]
* [[Jewish High Priest]]
* [[Jewish National Front]]
* [[Jigalong]]
* [[Jim Varney]]
* [[Jinshin War]]
* [[Joaquín Guzmán López]]
* [[Johann Klima]]
* [[John Chizoba Vincent]]
* [[John Roach]]
* [[John T. Myers]]
* [[John the Apostle]]
* [[Jokela school shooting]]
* [[Joker (The Dark Knight)]]
* [[Jonathan Ive]]
* [[Jonny Greenwood]]
* [[Jose Mari Chan]]
* [[Josef Haist]]
* [[Josef Hamerl]]
* [[Josef Hickersberger]]
* [[Joseph City, Arizona]]
* [[Julia Creek, Queensland]]
* [[Jundah, Queensland]]
* [[József Dunszt]]
* [[KSI vs. Logan Paul II]]
* [[Kailram]]
* [[Kakraita (village)]]
* [[Kaluga (sturgeon)]]
* [[Kalumburu]]
* [[Kapfenberg SV]]
* [[Karl Bortoli]]
* [[Karl Schott]]
* [[Karl Tekusch]]
* [[Karl Zankl]]
* [[Karloo, Western Australia]]
* [[Karpe]]
* [[Kashikuri Shrine]]
* [[Katanning]]
* [[Kate Spade New York]]
* [[Katja Wienerroither]]
* [[Kazimierz Ludwik Piłsudski]]
* [[Keke Palmer]]
* [[Kelly Scholz]]
* [[Kenne Fant]]
* [[Kenneth Branagh]]
* [[Kent Police]]
* [[Kenyaichthys]]
* [[Kerbal Space Program]]
* [[Kerim Alajbegovic]]
* [[Ketenylidene]]
* [[Kevin Federline]]
* [[Khalil Ahmad Saharanpuri]]
* [[Khanke]]
* [[Khongtekcha]]
* [[Khwarazmian invasion of Oman Gulf (1212-1215)]]
* [[Kichijōji]]
* [[Kigali]]
* [[Killing of Yasser Abu Shabab]]
* [[Kimberley (Western Australia)]]
* [[King George Island (Antarctica)]]
* [[King George V DLR station]]
* [[King Sejong Station]]
* [[Kingoonya]]
* [[Kingsauce]]
* [[Kinue Asanuma]]
* [[Kirby (character)]]
* [[Klepp]]
* [[Knights of the Round Table]]
* [[Knowledge graph]]
* [[Kofun]]
* [[Kojarena, Western Australia]]
* [[Kompira]]
* [[Korean Catholic Association]]
* [[Korean sandlance]]
* [[Kremser SC]]
* [[Kristin Harila]]
* [[Krødsherad]]
* [[Kudardup, Western Australia]]
* [[Kununurra, Western Australia]]
* [[Kuwohi]]
* [[LWVNH v. Kramer]]
* [[La Chapelle-Craonnaise]]
* [[La France insoumise]]
* [[La Tour-du-Pin]]
* [[Labriformes]]
* [[Lagrange, Western Australia]]
* [[Lalish]]
* [[Lambda calculus]]
* [[Lancashire]]
* [[Land Rover]]
* [[Landscape painting]]
* [[Lapstone, New South Wales]]
* [[Lassina Traoré (footballer, born 2007)]]
* [[Latin alphabet]]
* [[Laverton, Western Australia]]
* [[Le Griffon]]
* [[Leaf litter]]
* [[Leeuwin, Western Australia]]
* [[Leeuwin-Naturaliste National Park]]
* [[Leicestershire]]
* [[Leigh Creek, South Australia]]
* [[Lena Mukhina]]
* [[Lent]]
* [[Leonhard Machu]]
* [[Leonora, Western Australia]]
* [[Leopardus]]
* [[Leopold Giebisch]]
* [[Lepisosteiformes]]
* [[Lepisosteus]]
* [[Lesdain]]
* [[Lev Shomea]]
* [[Life Is Beautiful]]
* [[Light truck]]
* [[Lightning Ridge]]
* [[Like Phantoms, Forever]]
* [[Linear Pottery culture]]
* [[Lionel Shapiro]]
* [[List of Dexter's Laboratory episodes]]
* [[List of General Secretaries of the Soviet Union]]
* [[List of Shikinaisha in Shima Province]]
* [[List of barons and lords in Britain and Ireland today]]
* [[List of fugal works by Johann Sebastian Bach]]
* [[List of international goals scored by Sadio Mané]]
* [[List of international matches between Austria and Hungary]]
* [[List of mathematical symbols]]
* [[List of prehistoric bony fish genera]]
* [[List of search engines]]
* [[Little weed whiting]]
* [[Live at Wembley July 16, 1988]]
* [[Lombadina]]
* [[London Eye]]
* [[London Interdisciplinary School]]
* [[Longreach, Queensland]]
* [[Looma]]
* [[Loppa]]
* [[Los Pica Pica]]
* [[Luca Salsi]]
* [[Lucas Reiner]]
* [[Luce (mascot)]]
* [[Lugaid Mac Con]]
* [[Luka Sero]]
* [[Lyndhurst, South Australia]]
* [[Mac. Robertson Land]]
* [[Macquarie Island Station]]
* [[Magadhan Empire]]
* [[Magick]]
* [[Mahadzir Mohd Khir]]
* [[Mahomets Flats, Western Australia]]
* [[Mainland Japan]]
* [[Maitri (research station)]]
* [[Malagueta pepper]]
* [[Maleic anhydride]]
* [[Malik Asghar Ali Qaisar]]
* [[Man or bear]]
* [[Mangar (fish)]]
* [[Manjimup, Western Australia]]
* [[Mansudae]]
* [[Marble Bar]]
* [[Marc Platt (producer)]]
* [[Margaret Tudor]]
* [[Mario Party: The Top 100]]
* [[Mark Goulston]]
* [[Marker, Norway]]
* [[Martock]]
* [[Marwan Hadid]]
* [[Mary Ann Crowe]]
* [[Masala]]
* [[Mataranka, Northern Territory]]
* [[Mathematics education]]
* [[Mathieu Blondeau]]
* [[Matt Blumenthal]]
* [[Matt Katz-Bohen]]
* [[Mawlid]]
* [[Mayker Palacios]]
* [[Mazda CX-30]]
* [[Meeker, Oklahoma]]
* [[Meissa]]
* [[Merredin, Western Australia]]
* [[Meru, Western Australia]]
* [[Metriacanthosaurus]]
* [[Metricup, Western Australia]]
* [[Metropolitan Transportation Authority Police]]
* [[Michael Konsel]]
* [[Michele Ferrero]]
* [[Mid West (Western Australia)]]
* [[Midichlorian]]
* [[Midwest Political Science Association]]
* [[Mihashira Torii]]
* [[Mild intellectual disability]]
* [[Mildura]]
* [[Millennium Prize Problems]]
* [[Mindanao rasbora]]
* [[Minimum wage]]
* [[Minister of Sports]]
* [[Ministry of Education (China)]]
* [[Minnenooka, Western Australia]]
* [[Miracle Musical]]
* [[Mirim Horse Riding Club]]
* [[Mirza Mohammed Athar]]
* [[Mitsumine Shrine]]
* [[Mixed acid]]
* [[Modum]]
* [[Moe]]
* [[Mohammad Bagheri (general)]]
* [[Mohammad Pakpour]]
* [[Mohammed Rafi]]
* [[Molidae]]
* [[Molloy Island, Western Australia]]
* [[Moon wrasse]]
* [[Moresby, Western Australia]]
* [[Morphinan]]
* [[Moscow Center of SPARC Technologies (MCST)]]
* [[Moses Häusler]]
* [[Moss, Norway]]
* [[Mossad]]
* [[Mount Barker, Western Australia]]
* [[Mount Gambier]]
* [[Mount Magnet]]
* [[Mozambique Channel]]
* [[Mritunjay Kumar Tiwary]]
* [[Muhammad Ilyas Kandhlawi]]
* [[Muhammad Khudabanda]]
* [[Muhhadith]]
* [[Multiocular O]]
* [[Mungeranie, South Australia]]
* [[NA-185 Dera Ghazi Khan-II]]
* [[NGC 3521]]
* [[Naguib Sawiris]]
* [[Nancy Ajram]]
* [[Nannup, Western Australia]]
* [[Naomi Seibt]]
* [[Narvik]]
* [[Nasser El Sonbaty]]
* [[National Day Against Homophobia]]
* [[National flower of Uruguay]]
* [[Nelumbo lutea]]
* [[Neo-Mu'tazilites]]
* [[Neopentane]]
* [[Nesbyen]]
* [[Neumayer-III Station]]
* [[Nevis]]
* [[New World Order]]
* [[New Zealand English]]
* [[New shekel]]
* [[New vision]]
* [[Newhall estate]]
* [[Nickel Boys]]
* [[Niederanven]]
* [[Nillup, Western Australia]]
* [[Ninjapromo]]
* [[No quarter]]
* [[Noah Z. Jones]]
* [[Nobel Prize in Chemistry]]
* [[Nokia 5510]]
* [[Nonthaburi province]]
* [[Norbert Katz]]
* [[Nordkapp]]
* [[Nordre Land]]
* [[Nore og Uvdal]]
* [[Norseman, Western Australia]]
* [[North Yorkshire]]
* [[Northampton, Western Australia]]
* [[Notodden]]
* [[Nullagine]]
* [[Number Ones (video)]]
* [[Obaidullah Shadikhel]]
* [[Ocean sunfish]]
* [[Octagonal Kofun]]
* [[Odax]]
* [[Office for the Coordination of Humanitarian Affairs]]
* [[Oklahoma House of Representatives]]
* [[Omori (video game)]]
* [[Onion ring]]
* [[Onium ion]]
* [[Onslow, Western Australia]]
* [[Opel Vectra]]
* [[Oppo F17 Pro]]
* [[Organization of Iranian Kurdistan Struggle]]
* [[Orlando Bloom]]
* [[Orly]]
* [[Ornacieux]]
* [[Osmington, Western Australia]]
* [[Ottoman-Persian Wars]]
* [[Ottoman-Qajar War (1906-1907)]]
* [[Our Lady of Guadalupe]]
* [[Oxfordshire]]
* [[PICO-8]]
* [[Paintbrush]]
* [[Pallid sturgeon]]
* [[Palmer Station]]
* [[Panhandle, Texas]]
* [[Pannawonica]]
* [[Paramabhattaraka]]
* [[Paramount Pictures]]
* [[Paris-East Créteil University]]
* [[Parliament of South Australia]]
* [[Pat Nixon]]
* [[Patriots.eu]]
* [[Paul the Apostle]]
* [[Pear Deck]]
* [[Pelican Point, Western Australia]]
* [[Peloponnese]]
* [[Pemberton, Western Australia]]
* [[Pensacola Mountains]]
* [[Peppa Pig series 3]]
* [[Pepperjack cheese]]
* [[Pereslavl-Zalessky]]
* [[Periodic table]]
* [[Persepolis]]
* [[Peter Paul Rubens]]
* [[Peter Schöttel]]
* [[Pickleball]]
* [[Pierre Aubert]]
* [[Pine Creek, Northern Territory]]
* [[Plan de Rescate de Matemáticas]]
* [[Planettoon Tv]]
* [[Platonic solid]]
* [[Plug-in hybrid]]
* [[Point Samson]]
* [[Pokémon Pokopia]]
* [[Political positions of Robert F. Kennedy Jr.]]
* [[Polycyclic compound]]
* [[Polymerization]]
* [[Poon Hiu-wing]]
* [[Popular Forces]]
* [[Power cut]]
* [[Power factor]]
* [[Powerade]]
* [[Praça Soares Madruga]]
* [[President of Honduras]]
* [[Pressure suit]]
* [[Prevelly, Western Australia]]
* [[Profesor Julio Escudero Base]]
* [[Protarchaeopteryx]]
* [[Protest]]
* [[Protura]]
* [[Pseudoliparis swirei]]
* [[Puisseguin]]
* [[Puppy Bowl]]
* [[Puremba]]
* [[Pyongyang International Airport]]
* [[Quarter tone]]
* [[Quettabyte]]
* [[Quiñonería]]
* [[Qumran]]
* [[R62 (New York City Subway car)]]
* [[RDR2 Locomotives]]
* [[ROM hacking]]
* [[Race with Ryan]]
* [[Radu Theodoru]]
* [[Rahma Riad]]
* [[Rainbow cale]]
* [[Raje]]
* [[Rao Bika]]
* [[Rao Sahab]]
* [[Rasbora]]
* [[Razor]]
* [[Rebel News]]
* [[Redgate, Western Australia]]
* [[Reed pen]]
* [[Rekaz]]
* [[Relayball]]
* [[Repository (data)]]
* [[Resignation of Prince Andrew, Duke of York]]
* [[Reza Pahlavi, Crown Prince of Iran]]
* [[Rhinocerotoid]]
* [[Richmond, Queensland]]
* [[Ridge A]]
* [[Riemannian geometry]]
* [[Risala]]
* [[Robert Frind]]
* [[Robert Körner]]
* [[Robert Pavlicek]]
* [[Robert Sara]]
* [[Roberto Canessa]]
* [[Roberto Rossellini]]
* [[Robotic construction kit]]
* [[Rocco Zikovic]]
* [[Roeburne, Western Australia]]
* [[Rogers, Texas]]
* [[Roiffieux]]
* [[Rollag]]
* [[Rosa Brook, Western Australia]]
* [[Rosa Glen, Western Australia]]
* [[Ross Ice Shelf]]
* [[Round Kofun]]
* [[Royal Challenger Bangalore]]
* [[Rudi Hiden]]
* [[Rudolf Schlauf]]
* [[Rudolf Szanwald]]
* [[Rumble Fighter]]
* [[Runes]]
* [[Russian Republic]]
* [[Rutland]]
* [[Ryan Walters]]
* [[Réti Opening]]
* [[Rüstü Erdogan]]
* [[S. Shamsuddin]]
* [[SANAE IV]]
* [[SAS Viya]]
* [[SC Neusiedl am See]]
* [[SEAT Ibiza]]
* [[SK Vorwärts Steyr]]
* [[SV Gloggnitz]]
* [[Saint-Aubin-Fosse-Louvain]]
* [[Saint-Maurice-de-Beynost]]
* [[Saitama Seibu Lions]]
* [[Salon-de-Provence]]
* [[Sam Thompson]]
* [[Samsung Galaxy S series]]
* [[Samuel Gompers]]
* [[Samuel Lister, 1st Baron Masham]]
* [[San Pedro Claver church]]
* [[San Salvador]]
* [[Sandefjord]]
* [[Sandnes]]
* [[Sanel Kuljić]]
* [[Sarah Paine]]
* [[Saturn Outlook]]
* [[School year]]
* [[Schroeder, Western Australia]]
* [[Scott Base]]
* [[Scott River East, Western Australia]]
* [[Scott River, Western Australia]]
* [[Scottish clan]]
* [[Scramble for Africa]]
* [[Second Link Expressway]]
* [[Sero Brothers]]
* [[Sex therapy]]
* [[Shackleton Ice Shelf]]
* [[Shaggs' Own Thing]]
* [[Shaman]]
* [[Shangani Patrol]]
* [[Shiba Inu]]
* [[Shinto honorifics]]
* [[Shire of Augusta-Margaret River]]
* [[Shorts]]
* [[Shropshire]]
* [[Siege of Kinsale]]
* [[Sigdal]]
* [[Silver]]
* [[Silver bromide]]
* [[Silver cyanate]]
* [[Silverton, New South Wales]]
* [[Sinn Féin]]
* [[Sky Diver]]
* [[Sleeping Dogs]]
* [[Social Democratic Party (UK, 1990)]]
* [[Sogndal]]
* [[Soju]]
* [[Solanki Rajputs]]
* [[Somerset]]
* [[Soraya Esfandiary-Bakhtiary]]
* [[Sota Kitano]]
* [[Sour sugar]]
* [[South Bunbury, Western Australia]]
* [[South Burlington, Vermont]]
* [[South Geomagnetic Pole]]
* [[South Lebanon conflict (1985–2000)]]
* [[South Orkney Islands]]
* [[South Shetland Islands]]
* [[Southern lapwing]]
* [[Soviet Civil Administration]]
* [[Spanaway Junior High School shooting]]
* [[Square Kofun]]
* [[Staffordshire]]
* [[Standing Council of Scottish Chiefs]]
* [[Stavanger]]
* [[Stealth aircraft]]
* [[Stefan Skoumal]]
* [[Stefan Wagner]]
* [[Stefania Rivi]]
* [[Stereographic projection]]
* [[Steven V. Maksin]]
* [[Stjørdal]]
* [[Stoke Lucero]]
* [[Strong Bad's Cool Game for Attractive People]]
* [[Stygimoloch]]
* [[Sula, Møre og Romsdal]]
* [[Sulfoxide]]
* [[Sunella]]
* [[Sunnah]]
* [[Sunndal]]
* [[Super Animal Royale]]
* [[Super Mario Galaxy]]
* [[Suroz]]
* [[Surrey]]
* [[Susan Monarez]]
* [[Suzzane Cryer]]
* [[Svetlana Kuznetsova]]
* [[Syr Darya sturgeon]]
* [[São Paulo Metro]]
* [[São Paulo/Guarulhos International Airport]]
* [[Ségolène Royal]]
* [[Søndre Land]]
* [[TI-83 series]]
* [[TIC-80]]
* [[Tachycardia]]
* [[Tainrakuasuchus bellator]]
* [[Takashi Iizuka]]
* [[Takifugu]]
* [[Tamil Nadu]]
* [[Tampongate]]
* [[Tangkap Najib rally]]
* [[Tanto]]
* [[Taobao]]
* [[Tarzan (1999 movie)]]
* [[Tatsumi Kimishima]]
* [[Taurus (constellation)]]
* [[Te Wahipounamu]]
* [[Television network]]
* [[Telltale Incorporated]]
* [[Temple Terrace, Florida]]
* [[Teriberka]]
* [[Terra Nova Bay]]
* [[Territorial integrity]]
* [[Terry stop]]
* [[Tessellation]]
* [[Testaments of the Twelve Patriarchs]]
* [[The Ashes]]
* [[The Boy in the Striped Pyjamas]]
* [[The Brink's Job]]
* [[The Burren]]
* [[The Cat in the Hat (movie)]]
* [[The Charlie Brown and Snoopy Show]]
* [[The Dead Dance]]
* [[The Escapists]]
* [[The First Epistle of Clement to the Corinthians]]
* [[The Happy Lovers]]
* [[The Kleptocrats]]
* [[The Matrix Resurrections]]
* [[The Right Excellent]]
* [[The Taking of Pelham 123 (2009 movie)]]
* [[The Tree of Life (movie)]]
* [[The Twigs]]
* [[The Wordies]]
* [[Theodore Tugboat]]
* [[Thimphu]]
* [[Thirsk]]
* [[Thomas Starzl]]
* [[Thorn (letter)]]
* [[Three Munakata goddesses]]
* [[Three-spined stickleback]]
* [[Tilpa]]
* [[Tim Stokely]]
* [[Tincinae]]
* [[Titan triggerfish]]
* [[Titin]]
* [[Tom Price, Western Australia]]
* [[Tonpa Shenrab Miwoche]]
* [[Tony Gonzalez]]
* [[Tony Robbins]]
* [[Toretsk]]
* [[Toronto Argonauts]]
* [[Torre Rise]]
* [[Tory Green]]
* [[Toyōke Ōmikami]]
* [[Traditionalist theology (Islam)]]
* [[Tree (data structure)]]
* [[Treeton, Western Australia]]
* [[Trent Boult]]
* [[Triggerfish]]
* [[Triple Entente]]
* [[Tsukinami-no-Matsuri]]
* [[Tubemouth]]
* [[Tujunga Wash]]
* [[Tulsa King]]
* [[Tupolev Tu-124]]
* [[Tur Abdin]]
* [[Turkish war crimes]]
* [[Turoyo language]]
* [[Tyndall effect]]
* [[Tyne and Wear]]
* [[UEFA Women's Euro 2013]]
* [[USA (disambiguation)]]
* [[Umme Hassan]]
* [[Union Gurten]]
* [[Union Kleinmünchen]]
* [[United Nations Environment Programme]]
* [[United Parcel Service]]
* [[United States Associate Attorney General]]
* [[United States Department of Health and Human Services]]
* [[United States Department of Homeland Security]]
* [[United States Department of Justice]]
* [[United States Department of State]]
* [[United States Department of the Air Force]]
* [[United States Department of the Navy]]
* [[United States Deputy Attorney General]]
* [[United States Under Secretary of the Navy]]
* [[United States invasion of Grenada]]
* [[Universidade Regional do Cariri]]
* [[University of Indonesia]]
* [[University of Nevada, Las Vegas]]
* [[Unzen Onsen Shrine]]
* [[Urban area]]
* [[Usher, Western Australia]]
* [[Uttar Pradesh]]
* [[VTR (Chile)]]
* [[Vagabonds!]]
* [[Valea Moldovei]]
* [[Van, Turkey]]
* [[Vasishthiputra Vasusena]]
* [[Vestre Toten]]
* [[Vestvågøy]]
* [[Victoria Land]]
* [[Vietnam National University, Hanoi]]
* [[Vietnamese food]]
* [[Villa Las Estrellas]]
* [[Vimba elongata]]
* [[Vinje]]
* [[Vinson Elkins]]
* [[Vinyl norbornene]]
* [[Violence against men]]
* [[Violetta Shostak]]
* [[Visionary: The Video Singles]]
* [[Vistula–Oder offensive]]
* [[Vittoria, Western Australia]]
* [[Volda]]
* [[Volkswagen T-Cross]]
* [[Volkswagen T-Roc]]
* [[Volkswagen Taigo]]
* [[Volvo XC40]]
* [[Walter Kogler]]
* [[Walter Skocik]]
* [[Wanaaring, New South Wales]]
* [[Warmun Community, Western Australia]]
* [[Warner Glen, Western Australia]]
* [[Warwickshire]]
* [[We (Cyrillic)]]
* [[Weaponization of antisemitism]]
* [[Web browser]]
* [[Wedding of Prince Charles and Lady Diana Spencer]]
* [[Wedgie]]
* [[Welfare state]]
* [[Welsh Football League]]
* [[Wenzhou]]
* [[West Antarctic Ice Sheet]]
* [[West Antarctica]]
* [[West Midlands (county)]]
* [[White Cliffs, New South Wales]]
* [[Wickham, Western Australia]]
* [[Wiener Sport-Club]]
* [[Wilcannia]]
* [[Wilkes Land]]
* [[William Creek, South Australia]]
* [[Wiltshire]]
* [[Windorah]]
* [[Winifred Atwell]]
* [[Winton, Queensland]]
* [[Witchcliffe, Western Australia]]
* [[Withers, Western Australia]]
* [[Wolfgang Mair]]
* [[Woomera, South Australia]]
* [[Worcestershire]]
* [[Wyndham, Western Australia]]
* [[X&Y (album)]]
* [[Xinhua Bookstore]]
* [[YSCC Yokohama]]
* [[Yachad (political party)]]
* [[Yadav]]
* [[Yamato Ōkunitama Shrine]]
* [[Yasoob Abbas]]
* [[Yeat]]
* [[Yebble, Western Australia]]
* [[Yumna Marwan]]
* [[Yungngora]]
* [[Yunus Jaunpuri]]
* [[Yuri (manga)]]
* [[Zabeel Stadium]]
* [[Zakariyya Kandhlawi]]
* [[Zezé Procópio]]
* [[Zinzi Coogler]]
* [[Zlatoust]]
* [[Zucchelli Station]]
* [[Zune Software]]
* [[Émilienne Malfatto]]
* [[Évreux]]
* [[Øvre Eiker]]
* [[Ōharae no Kotoba]]
* [[Ōmiya-no-Me]]
* [[Śūnyatā]]
* [[Şemdinli]]
9va36nd9me6n74gwdjzkfhkuyvi584m
File:Clear testtttttttt.svg
6
174751
737279
2026-04-08T21:00:35Z
Ladsgroup
2217
737279
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
User:ToluAyod/Starter kit/Welcome banner
2
174752
737283
2026-04-08T22:12:40Z
ToluAyod
69650
Initialised by StarterKit tool — ready for translation
737283
wikitext
text/x-wiki
<div style="text-align: center; font-family: 'Linux Libertine', Georgia, Times, serif; margin: 1.5em 0;">
<span style="font-size: 2.3em; line-height: 1.2;">Welcome to {{#language:{{PAGELANGUAGE}}}} Wikipedia</span><br/>
<span style="font-size: 1.1em; color: #54595d;">The free encyclopedia that anyone can edit</span><br/>
<span style="font-size: 0.95em; color: #72777d; margin-top: 0.5em; display: inline-block;">{{NUMBEROFACTIVEUSERS}} active editors • '''{{NUMBEROFARTICLES}}''' articles in this {{SITENAME}}</span>
</div>
<noinclude>[[Category:Starter Kit templates]][[Category:Main page templates]]</noinclude>
stpbp0i7ynhb72e8p1kbljgu8dv2uw1
User:ToluAyod/Starter kit/Lo jo oni
2
174753
737284
2026-04-08T22:14:06Z
ToluAyod
69650
Initialised by StarterKit tool — ready for translation
737284
wikitext
text/x-wiki
<div style="border:1px solid #cedff2;border-radius:4px;padding:8px;background:#f5faff;margin-bottom:2px;">
<div style="padding:4px 12px;margin-bottom:8px;border:1px solid #a3b0bf;border-radius:4px;background:#cedff2;">'''On this day'''</div>
<!-- Add 2–3 historical events relevant to your community or topic area.
No need to update daily — refresh occasionally as your wiki grows.
To add an image: [[File:Filename.jpg|80px|right|alt=description]] -->
Add a date here
* Add a historical event and link to a relevant article here.
* Add a historical event and link to a relevant article here.
</div>
[[Category:Starter Kit templates]][[Category:Main page templates]]
oawkmcoyobjvtr2l018429c6lkvtbv7
User:Mr. Ibrahem/zxx
2
174754
737288
2026-04-09T04:13:58Z
Mr. Ibrahem
19267
Created page with "{{تقدم تطوير مقالة مشروع ويكي|المصدر=نعم|المستخدم=نعم|العرض=90%| {{تقدم تطوير مقالة مشروع ويكي/صف|م=1|المقالة=أحمد تازودينوف|المصدر=[[:en:Akhmed Tazhudinov]]|الهدف=2|المستخدم=؟؟}} {{تقدم تطوير مقالة مشروع ويكي/صف|م=2|المقالة=معهد القدس لأبحاث السياسات|المصدر=:en:Jerusalem Institute for Policy Re..."
737288
wikitext
text/x-wiki
{{تقدم تطوير مقالة مشروع ويكي|المصدر=نعم|المستخدم=نعم|العرض=90%|
{{تقدم تطوير مقالة مشروع ويكي/صف|م=1|المقالة=أحمد تازودينوف|المصدر=[[:en:Akhmed Tazhudinov]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=2|المقالة=معهد القدس لأبحاث السياسات|المصدر=[[:en:Jerusalem Institute for Policy Research]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=3|المقالة=نادي جابال إيكوني|المصدر=[[:en:Djabal Club d'Iconi]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=4|المقالة=نادي نغايا دي مديه|المصدر=[[:en:Ngaya Club de Mdé]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=5|المقالة=دوموني|المصدر=[[:en:Domoni]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=6|المقالة=بطولة جزر القمر لكرة السلة|المصدر=[[:en:Comoros Basketball Championship]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=7|المقالة=العلاقات الصينية القمرية|المصدر=[[:en:China–Comoros relations]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=8|المقالة=آلية التحقيق المشتركة بين منظمة حظر الأسلحة الكيميائية والأمم المتحدة|المصدر=[[:en:OPCW-UN Joint Investigative Mechanism]]|الهدف=2|المستخدم=زين العابدين 22}}
fzvals7mmq4g9m80w8m44g6yzmqyiq2
737289
737288
2026-04-09T04:14:59Z
Mr. Ibrahem
19267
737289
wikitext
text/x-wiki
{{تقدم تطوير مقالة مشروع ويكي|المصدر=نعم|المستخدم=نعم|العرض=90%|
{{تقدم تطوير مقالة مشروع ويكي/صف|م=1|المقالة=أحمد تازودينوف|المصدر=[[:en:Akhmed Tazhudinov]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=2|المقالة=معهد القدس لأبحاث السياسات|المصدر=[[:en:Jerusalem Institute for Policy Research]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=3|المقالة=نادي جابال إيكوني|المصدر=[[:en:Djabal Club d'Iconi]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=4|المقالة=نادي نغايا دي مديه|المصدر=[[:en:Ngaya Club de Mdé]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=5|المقالة=دوموني|المصدر=[[:en:Domoni]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=6|المقالة=بطولة جزر القمر لكرة السلة|المصدر=[[:en:Comoros Basketball Championship]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=7|المقالة=العلاقات الصينية القمرية|المصدر=[[:en:China–Comoros relations]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=8|المقالة=آلية التحقيق المشتركة بين منظمة حظر الأسلحة الكيميائية والأمم المتحدة|المصدر=[[:en:OPCW-UN Joint Investigative Mechanism]]|الهدف=2|المستخدم=زين العابدين 22}}
|}
tkiecjc8z813iro1jidfdj11jtoq5la
737292
737289
2026-04-09T04:17:24Z
Mr. Ibrahem
19267
737292
wikitext
text/x-wiki
{{تقدم تطوير مقالة مشروع ويكي|المصدر=نعم|المستخدم=نعم|العرض=90%|
{{تقدم تطوير مقالة مشروع ويكي/صف|م=1|المقالة=أحمد تازودينوف|المصدر=[[:en:Akhmed Tazhudinov]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=2|المقالة=معهد القدس لأبحاث السياسات|المصدر=[[:en:Jerusalem Institute for Policy Research]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=3|المقالة=نادي جابال إيكوني|المصدر=[[:en:Djabal Club d'Iconi]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=4|المقالة=نادي نغايا دي مديه|المصدر=[[:en:Ngaya Club de Mdé]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=5|المقالة=دوموني|المصدر=[[:en:Domoni]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=6|المقالة=بطولة جزر القمر لكرة السلة|المصدر=[[:en:Comoros Basketball Championship]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=7|المقالة=العلاقات الصينية القمرية|المصدر=[[:en:China–Comoros relations]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=8|المقالة=آلية التحقيق المشتركة بين منظمة حظر الأسلحة الكيميائية والأمم المتحدة|المصدر=[[:en:OPCW-UN Joint Investigative Mechanism]]|الهدف=2|المستخدم=زين العابدين 22}}
}}
iz2shtszrb58atz8rk1sd0ew7eed09y
737295
737292
2026-04-09T04:19:02Z
Mr. Ibrahem
19267
737295
wikitext
text/x-wiki
{{تقدم تطوير مقالة مشروع ويكي|المصدر=نعم|المستخدم=نعم|العرض=90%|
{{تقدم تطوير مقالة مشروع ويكي/صف|م=1|المقالة=أحمد تازودينوف|المصدر=[[:en:Akhmed Tazhudinov]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=2|المقالة=معهد القدس لأبحاث السياسات|المصدر=[[:en:Jerusalem Institute for Policy Research]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=3|المقالة=نادي جابال إيكوني|المصدر=[[:en:Djabal Club d'Iconi]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=4|المقالة=نادي نغايا دي مديه|المصدر=[[:en:Ngaya Club de Mdé]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=5|المقالة=دوموني|المصدر=[[:en:Domoni]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=6|المقالة=بطولة جزر القمر لكرة السلة|المصدر=[[:en:Comoros Basketball Championship]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=7|المقالة=العلاقات الصينية القمرية|المصدر=[[:en:China–Comoros relations]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=8|المقالة=آلية التحقيق المشتركة بين منظمة حظر الأسلحة الكيميائية والأمم المتحدة|المصدر=[[:en:OPCW-UN Joint Investigative Mechanism]]|الهدف=2|المستخدم=زين العابدين 22}}
}}
39o159u92qfy2r012l96napumgh68jf
737296
737295
2026-04-09T04:19:10Z
Mr. Ibrahem
19267
حجز مقالة [[en:Akhmed Tazhudinov]] عبر أداة التطوير
737296
wikitext
text/x-wiki
{{تقدم تطوير مقالة مشروع ويكي|المصدر=نعم|المستخدم=نعم|العرض=90%|
{{تقدم تطوير مقالة مشروع ويكي/صف|م=1|المقالة=أحمد تازودينوف|المصدر=[[:en:Akhmed Tazhudinov]]|الهدف=2|المستخدم=Mr. Ibrahem}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=2|المقالة=معهد القدس لأبحاث السياسات|المصدر=[[:en:Jerusalem Institute for Policy Research]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=3|المقالة=نادي جابال إيكوني|المصدر=[[:en:Djabal Club d'Iconi]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=4|المقالة=نادي نغايا دي مديه|المصدر=[[:en:Ngaya Club de Mdé]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=5|المقالة=دوموني|المصدر=[[:en:Domoni]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=6|المقالة=بطولة جزر القمر لكرة السلة|المصدر=[[:en:Comoros Basketball Championship]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=7|المقالة=العلاقات الصينية القمرية|المصدر=[[:en:China–Comoros relations]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=8|المقالة=آلية التحقيق المشتركة بين منظمة حظر الأسلحة الكيميائية والأمم المتحدة|المصدر=[[:en:OPCW-UN Joint Investigative Mechanism]]|الهدف=2|المستخدم=زين العابدين 22}}
}}
tp2ghaew3xhl8c8t1anx5lz8t8cfd41
737297
737296
2026-04-09T04:31:10Z
Mr. Ibrahem
19267
737297
wikitext
text/x-wiki
{{تقدم تطوير مقالة مشروع ويكي|المصدر=نعم|المستخدم=نعم|العرض=90%|
{{تقدم تطوير مقالة مشروع ويكي/صف|م=1|المقالة=أحمد تازودينوف|المصدر=[[:en:Akhmed Tazhudinov]]|الهدف=2|المستخدم=Mr. Ibrahem}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=2|المقالة=معهد القدس لأبحاث السياسات|المصدر=[[:en:Jerusalem Institute for Policy Research]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=3|المقالة=نادي جابال إيكوني|المصدر=[[:en:Djabal Club d'Iconi]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=4|المقالة=نادي نغايا دي مديه|المصدر=[[:en:Ngaya Club de Mdé]]|الهدف=2|المستخدم=Mr. Ibrahem}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=5|المقالة=دوموني|المصدر=[[:en:Domoni]]|الهدف=2|المستخدم=؟؟}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=6|المقالة=بطولة جزر القمر لكرة السلة|المصدر=[[:en:Comoros Basketball Championship]]|الهدف=2|المستخدم=Ghada Badawi}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=7|المقالة=العلاقات الصينية القمرية|المصدر=[[:en:China–Comoros relations]]|الهدف=2|المستخدم=زين العابدين 22}}
{{تقدم تطوير مقالة مشروع ويكي/صف|م=8|المقالة=آلية التحقيق المشتركة بين منظمة حظر الأسلحة الكيميائية والأمم المتحدة|المصدر=[[:en:OPCW-UN Joint Investigative Mechanism]]|الهدف=2|المستخدم=زين العابدين 22}}
}}
6cagindyotecxx7auhg92bochdy6l7j
Template:تقدم تطوير مقالة مشروع ويكي/صف
10
174755
737290
2026-04-09T04:15:29Z
Mr. Ibrahem
19267
Created page with "<includeonly>|- |style="text-align:center"|{{{م|}}} |style="text-align:center"|[[{{{المقالة|}}}]]<!-- <sup>{{#لومعادلة:{{#invoke:redirect|isRedirect|{{{المقالة|}}}}}|yes|[[ملف:Breezeicons-actions-22-window-close.svg|19بك|وصلة=|هذه تحويلة! فضلًا عدل قيمة الوسيط إلى المقالة الهدف.]] }}</sup> --> {{#لو:{{{المصدر|}}}| {{!}}style="text-align:center"{{!}} {{{المصدر|}}} }} |style="text-align..."
737290
wikitext
text/x-wiki
<includeonly>|-
|style="text-align:center"|{{{م|}}}
|style="text-align:center"|[[{{{المقالة|}}}]]<!-- <sup>{{#لومعادلة:{{#invoke:redirect|isRedirect|{{{المقالة|}}}}}|yes|[[ملف:Breezeicons-actions-22-window-close.svg|19بك|وصلة=|هذه تحويلة! فضلًا عدل قيمة الوسيط إلى المقالة الهدف.]] }}</sup> -->
{{#لو:{{{المصدر|}}}|
{{!}}style="text-align:center"{{!}} {{{المصدر|}}}
}}
|style="text-align:center"|{{#لو:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}}}|{{#لو:{{{الحجم الأصلي|}}}|{{formatnum:{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}}}|{{{الهدف|15}}}000}}}}/'''{{PAGESIZE:{{{المقالة|}}}|R}}''' بايت
{{#لو:{{{المستخدم|}}}|
{{!}}style="text-align:center"{{!}}{{مس|{{{المستخدم|}}}}}}}
|style="line-height:1em; margin:0px;"|
{{شريط تقدم بسيط|العرض=150px|{{PAGESIZE:{{{المقالة|}}}|R}}|{{#لو:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}|R}}|{{#لو:{{{الحجم الأصلي|}}}|{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}|{{{الهدف|15}}}000}}}}}}
|
{{#لوتعبير:{{PAGESIZE:{{{المقالة|}}}|R}}=0|[[ملف:No Cross.svg|20px|وصلة=|لم تُنشأ بعد!]]|
{{#لوتعبير:{{PAGESIZE:{{{المقالة|}}}|R}} >= {{#لو:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}|R}}|{{#لو:{{{الحجم الأصلي|}}}|{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}|{{{الهدف|15}}}000}}}}|[[ملف:Green Checkmark Circle.svg|20px|وصلة=|جاهزة!]]|[[ملف:Writing Circle Yellow.svg|20px|وصلة=|تحت التطوير!]]}}
}}</includeonly><noinclude>'''ملاحظة:''' هذا القالب لا يُستخدم لوحده، وإنما يعمل مكمّلًا لقالب {{قا|تقدم تطوير مقالة مشروع ويكي}}. فضلًا راجع شرح القالب لتفاصيل استخدامه.
</noinclude>
q4tt95x0x34wd6oactne8bdz3grgup1
737291
737290
2026-04-09T04:16:58Z
Mr. Ibrahem
19267
737291
wikitext
text/x-wiki
<includeonly>|-
|style="text-align:center"|{{{م|}}}
|style="text-align:center"|[[{{{المقالة|}}}]]<!-- <sup>{{#لومعادلة:{{#invoke:redirect|isRedirect|{{{المقالة|}}}}}|yes|[[file:Breezeicons-actions-22-window-close.svg|19بك|وصلة=|هذه تحويلة! فضلًا عدل قيمة الوسيط إلى المقالة الهدف.]] }}</sup> -->
{{#if:{{{المصدر|}}}|
{{!}}style="text-align:center"{{!}} {{{المصدر|}}}
}}
|style="text-align:center"|{{#if:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}}}|{{#if:{{{الحجم الأصلي|}}}|{{formatnum:{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}}}|{{{الهدف|15}}}000}}}}/'''{{PAGESIZE:{{{المقالة|}}}|R}}''' بايت
{{#if:{{{المستخدم|}}}|
{{!}}style="text-align:center"{{!}}{{مس|{{{المستخدم|}}}}}}}
|style="line-height:1em; margin:0px;"|
{{شريط تقدم بسيط|العرض=150px|{{PAGESIZE:{{{المقالة|}}}|R}}|{{#if:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}|R}}|{{#if:{{{الحجم الأصلي|}}}|{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}|{{{الهدف|15}}}000}}}}}}
|
{{#ifeq:{{PAGESIZE:{{{المقالة|}}}|R}}=0|[[file:No Cross.svg|20px|وصلة=|لم تُنشأ بعد!]]|
{{#ifeq:{{PAGESIZE:{{{المقالة|}}}|R}} >= {{#if:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}|R}}|{{#if:{{{الحجم الأصلي|}}}|{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}|{{{الهدف|15}}}000}}}}|[[file:Green Checkmark Circle.svg|20px|وصلة=|جاهزة!]]|[[file:Writing Circle Yellow.svg|20px|وصلة=|تحت التطوير!]]}}
}}</includeonly><noinclude>'''ملاحظة:''' هذا القالب لا يُستخدم لوحده، وإنما يعمل مكمّلًا لقالب {{قا|تقدم تطوير مقالة مشروع ويكي}}. فضلًا راجع شرح القالب لتفاصيل استخدامه.
</noinclude>
o0z451amm0dpjlk5suyc18qhgr61pgz
737294
737291
2026-04-09T04:18:35Z
Mr. Ibrahem
19267
737294
wikitext
text/x-wiki
<includeonly>|-
|style="text-align:center"|{{{م|}}}
|style="text-align:center"|[[{{{المقالة|}}}]]<!-- <sup>{{#لومعادلة:{{#invoke:redirect|isRedirect|{{{المقالة|}}}}}|yes|[[file:Breezeicons-actions-22-window-close.svg|19بك|وصلة=|هذه تحويلة! فضلًا عدل قيمة الوسيط إلى المقالة الهدف.]] }}</sup> -->
{{#if:{{{المصدر|}}}|
{{!}}style="text-align:center"{{!}} {{{المصدر|}}}
}}
|style="text-align:center"|{{#if:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}}}|{{#if:{{{الحجم الأصلي|}}}|{{formatnum:{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}}}|{{{الهدف|15}}}000}}}}/'''{{PAGESIZE:{{{المقالة|}}}|R}}''' بايت
{{#if:{{{المستخدم|}}}|
{{!}}style="text-align:center"{{!}}{{{المستخدم|}}}}}
|style="line-height:1em; margin:0px;"|
{{شريط تقدم بسيط|العرض=150px|{{PAGESIZE:{{{المقالة|}}}|R}}|{{#if:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}|R}}|{{#if:{{{الحجم الأصلي|}}}|{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}|{{{الهدف|15}}}000}}}}}}
|
{{#ifeq:{{PAGESIZE:{{{المقالة|}}}|R}}=0|[[file:No Cross.svg|20px|وصلة=|لم تُنشأ بعد!]]|
{{#ifeq:{{PAGESIZE:{{{المقالة|}}}|R}} >= {{#if:{{{الحجم المستهدف|}}}|{{formatnum:{{{الحجم المستهدف|}}}|R}}|{{#if:{{{الحجم الأصلي|}}}|{{#تعبير:{{{الحجم الأصلي|}}}+{{{شرط الحجم|5000}}}}}|{{{الهدف|15}}}000}}}}|[[file:Green Checkmark Circle.svg|20px|وصلة=|جاهزة!]]|[[file:Writing Circle Yellow.svg|20px|وصلة=|تحت التطوير!]]}}
}}</includeonly><noinclude>'''ملاحظة:''' هذا القالب لا يُستخدم لوحده، وإنما يعمل مكمّلًا لقالب {{قا|تقدم تطوير مقالة مشروع ويكي}}. فضلًا راجع شرح القالب لتفاصيل استخدامه.
</noinclude>
duv2almtivohpix3nsnv8hsjfeh29gu
Template:تقدم تطوير مقالة مشروع ويكي
10
174756
737293
2026-04-09T04:17:48Z
Mr. Ibrahem
19267
Created page with "{| class="wikitable sortable" style="width: {{{العرض|80%}}}; margin-right:auto; margin-left:auto; font-size:95%;border-bottom:3px solid #CCC; background:transparent; text-align:center; <!-- line-height:1em; -->" ! style="width:10px; vertical-align:center;" |م ! style="vertical-align:center;" |المقالة {{#if:{{{المصدر|}}}| ! style="vertical-align:center;"{{!}}المصدر }} ! style="width:150px; vertical-align:center;" |حجم المقالة {{#if:{{{..."
737293
wikitext
text/x-wiki
{| class="wikitable sortable" style="width: {{{العرض|80%}}}; margin-right:auto; margin-left:auto; font-size:95%;border-bottom:3px solid #CCC; background:transparent; text-align:center; <!-- line-height:1em; -->"
! style="width:10px; vertical-align:center;" |م
! style="vertical-align:center;" |المقالة
{{#if:{{{المصدر|}}}|
! style="vertical-align:center;"{{!}}المصدر
}}
! style="width:150px; vertical-align:center;" |حجم المقالة
{{#if:{{{المستخدم|}}}|
! style="width:125px; vertical-align:center;"{{!}}مسندة إلى...
}}
! style="width:155px; vertical-align:center;" |نسبة التقدم
! style="width:25px; vertical-align:center;"|الحالة
|-
{{{1|}}}
|}
<noinclude>{{توثيق}}</noinclude>
j6zr0b5zw0i47iehgsc4tw5vxa08f2p
User:PieWriter/test.js
2
174757
737299
2026-04-09T06:12:24Z
PieWriter
72123
Created page with "// <nowiki> mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui'],function(){ var localApi=new mw.Api(); var metaApi=new mw.ForeignApi('https://meta.wikimedia.org/w/api.php'); function addButton(){ mw.util.addPortletLink('p-tb','#','Find redirects','t-findredirects','List broken redirects'); $('#t-findredirects').on('click',function(e){ e.preventDefault(); showBrokenRedirects(); }); } function s..."
737299
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui'],function(){
var localApi=new mw.Api();
var metaApi=new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');
function addButton(){
mw.util.addPortletLink('p-tb','#','Find redirects','t-findredirects','List broken redirects');
$('#t-findredirects').on('click',function(e){
e.preventDefault();
showBrokenRedirects();
});
}
function showBrokenRedirects(){
localApi.get({
action:'query',
list:'querypage',
qppage:'BrokenRedirects',
qplimit:'max'
}).done(function(data){
var redirects=data.query.querypage.results;
var overlay=$('<div>').css({
position:'fixed',top:0,left:0,width:'100%',height:'100%',
background:'rgba(0,0,0,0.6)','z-index':9998
});
var menu=$('<div>').css({
position:'fixed',top:'50%',left:'50%',
transform:'translate(-50%,-50%)',
background:'#fff',
padding:'0',
'z-index':9999,
width:'700px',
'max-height':'85%',
display:'flex',
'flex-direction':'column',
'border-radius':'10px',
overflow:'hidden',
'box-shadow':'0 8px 24px rgba(0,0,0,0.4)'
});
var header=$('<div>').css({
padding:'12px 16px',
background:'#2a4b8d',
color:'#fff',
display:'flex',
'justify-content':'space-between',
'align-items':'center'
});
header.append($('<span>').text('Broken Redirects Manager').css({'font-weight':'bold'}));
var closeBtn=$('<button>').text('✖').css({
border:'none',background:'transparent',color:'#fff','font-size':'1.2em',cursor:'pointer'
}).on('click',function(){overlay.remove();menu.remove();});
header.append(closeBtn);
var searchBar=$('<input>').attr('placeholder','Search redirects...').css({
width:'calc(100% - 20px)',
margin:'10px',
padding:'6px',
border:'1px solid #ccc',
'border-radius':'6px'
});
var listContainer=$('<div>').css({
overflow:'auto',
flex:'1',
padding:'10px'
});
redirects.forEach(function(item){
var page=item.title;
var ns=item.ns;
if(ns===2||ns===3||ns===118){return;}
localApi.get({action:'query',titles:page,prop:'info'}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){return;}
var entry=$('<div>').css({
display:'flex',
'justify-content':'space-between',
'align-items':'center',
padding:'8px',
margin:'4px 0',
background:'#f8f9fa',
'border-radius':'6px',
'transition':'0.2s'
}).hover(function(){
$(this).css('background','#e9ecef');
},function(){
$(this).css('background','#f8f9fa');
});
var title=$('<span>').text(page).css({
'flex':'1',
'font-size':'0.95em'
});
var btnGroup=$('<div>').css({
display:'flex',
gap:'4px'
});
function makeBtn(label,color,handler){
return $('<button>').text(label).css({
padding:'4px 8px',
border:'none',
'border-radius':'4px',
background:color,
color:'#fff',
cursor:'pointer',
'font-size':'0.8em'
}).on('click',handler);
}
btnGroup.append(
makeBtn('GSR','#6c757d',function(){requestDeletion(page);}),
makeBtn('GSR+Tag','#495057',function(){requestDeletion(page);addDeleteTag(page,true);}),
makeBtn('Tag','#0d6efd',function(){addDeleteTag(page,false);}),
makeBtn('Delete','#dc3545',function(){deletePage(page);}),
makeBtn('Change','#198754',function(){changeRedirectTarget(page);})
);
entry.append(title,btnGroup);
listContainer.append(entry);
});
});
searchBar.on('input',function(){
var val=$(this).val().toLowerCase();
listContainer.children().each(function(){
var txt=$(this).text().toLowerCase();
$(this).toggle(txt.indexOf(val)!==-1);
});
});
menu.append(header,searchBar,listContainer);
$('body').append(overlay).append(menu);
});
}
function requestDeletion(page){
var lang=mw.config.get('wgContentLanguage');
var dbname=mw.config.get('wgDBname');
var prefix='';
if(dbname.includes('wikibooks'))prefix='b:'+lang+':';
else if(dbname.includes('wikiquote'))prefix='q:'+lang+':';
else if(dbname.includes('wikiversity'))prefix='wikiversity:'+lang+':';
else if(dbname.includes('wikisource'))prefix='wikisource:'+lang+':';
else if(dbname.includes('wikivoyage'))prefix='voy:'+lang+':';
else if(dbname.includes('wikispecies'))prefix='wikispecies:';
else if(dbname.includes('wiktionary'))prefix='wikt:'+lang+':';
else if(dbname.includes('wikinews'))prefix='n:'+lang+':';
else if(dbname.includes('commonswiki'))prefix='commons:';
else if(dbname.includes('metawiki'))prefix='m:';
else if(dbname.includes('mediawiki'))prefix='mw:';
else prefix=lang+':';
var formatted='* [[:'+prefix+page+']]: Broken redirect. <small>[[:m:User:PieWriter/BR.js|BR]]</small> ~~~~';
metaApi.postWithToken('csrf',{
action:'edit',
title:'Global sysops/Requests',
summary:'Requesting speedy deletion of broken redirect [[User:PieWriter/BR.js|using tool]]',
appendtext:'\n'+formatted
}).done(function(){alert('Deletion request added for '+page+' on Meta-Wiki');});
}
function addDeleteTag(page,gsr){
var tagText=gsr?'{{delete|Broken redirect|delete gsr}}':'{{delete|Broken redirect}}';
localApi.postWithToken('csrf',{
action:'edit',
title:page,
summary:'Tagging broken redirect for deletion ([[:m:User:PieWriter/BR.js|BR]])',
prependtext:tagText+'\n'
}).done(function(){alert('Added deletion tag to '+page+(gsr?' (with gsr)':''));});
}
function deletePage(page){
localApi.postWithToken('csrf',{
action:'delete',
title:page,
reason:'Broken redirect ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){alert('Page '+page+' deleted');})
.fail(function(){alert('Failed to delete '+page+'. You may need admin rights.');});
}
function changeRedirectTarget(page){
var newTarget=prompt('Enter new redirect target for '+page+':');
if(!newTarget)return;
localApi.get({
action:'query',
titles:newTarget
}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){
if(!confirm('Target page does not exist. Continue anyway?'))return;
}
localApi.get({
action:'query',
prop:'revisions',
titles:page,
rvprop:'content',
rvslots:'main',
formatversion:2
}).done(function(data){
var content=data.query.pages[0].revisions[0].slots.main.content;
var newContent;
if(/^#redirect\s*\[\[.*?\]\]/i.test(content)){
newContent=content.replace(/^#redirect\s*\[\[.*?\]\]/i,'#REDIRECT [['+newTarget+']]');
}else{
newContent='#REDIRECT [['+newTarget+']]\n'+content;
}
localApi.postWithToken('csrf',{
action:'edit',
title:page,
text:newContent,
summary:'Changing redirect target ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){
alert('Redirect target for '+page+' changed to '+newTarget);
}).fail(function(){
alert('Failed to change redirect target for '+page);
});
});
});
}
addButton();
});
// </nowiki>
smwa32wa2lbsdhthyz29ahzwv6235zz
737304
737299
2026-04-09T06:20:54Z
PieWriter
72123
737304
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui'],function(){
var localApi=new mw.Api();
var metaApi=new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');
function addButton(){
mw.util.addPortletLink('p-tb','#','Find redirects','t-findredirects','List broken redirects');
$('#t-findredirects').on('click',function(e){
e.preventDefault();
showBrokenRedirects();
});
}
function showBrokenRedirects(){
localApi.get({
action:'query',
list:'querypage',
qppage:'BrokenRedirects',
qplimit:'max'
}).done(function(data){
var redirects=data.query.querypage.results;
var overlay=$('<div>').css({
position:'fixed',top:0,left:0,width:'100%',height:'100%',
background:'rgba(0,0,0,0.6)','z-index':9998
});
var menu=$('<div>').css({
position:'fixed',top:'50%',left:'50%',
transform:'translate(-50%,-50%)',
background:'#fff',
padding:'0',
'z-index':9999,
width:'700px',
'max-height':'85%',
display:'flex',
'flex-direction':'column',
'border-radius':'10px',
overflow:'hidden',
'box-shadow':'0 8px 24px rgba(0,0,0,0.4)'
});
var header=$('<div>').css({
padding:'12px 16px',
background:'#2a4b8d',
color:'#fff',
display:'flex',
'justify-content':'space-between',
'align-items':'center'
});
header.append($('<span>').text('Broken Redirects Manager').css({'font-weight':'bold'}));
var closeBtn=$('<button>').text('✖').css({
border:'none',background:'transparent',color:'#fff','font-size':'1.2em',cursor:'pointer'
}).on('click',function(){overlay.remove();menu.remove();});
header.append(closeBtn);
var searchBar=$('<input>').attr('placeholder','Search for redirects...').css({
width:'calc(100% - 20px)',
margin:'10px',
padding:'6px',
border:'1px solid #ccc',
'border-radius':'6px'
});
var listContainer=$('<div>').css({
overflow:'auto',
flex:'1',
padding:'10px'
});
redirects.forEach(function(item){
var page=item.title;
var ns=item.ns;
if(ns===2||ns===3||ns===118){return;}
localApi.get({action:'query',titles:page,prop:'info'}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){return;}
var entry=$('<div>').css({
display:'flex',
'justify-content':'space-between',
'align-items':'center',
padding:'8px',
margin:'4px 0',
background:'#f8f9fa',
'border-radius':'6px',
'transition':'0.2s'
}).hover(function(){
$(this).css('background','#e9ecef');
},function(){
$(this).css('background','#f8f9fa');
});
var title=$('<span>').text(page).css({
'flex':'1',
'font-size':'0.95em'
});
var btnGroup=$('<div>').css({
display:'flex',
gap:'4px'
});
function makeBtn(label,color,handler){
return $('<button>').text(label).css({
padding:'4px 8px',
border:'none',
'border-radius':'4px',
background:color,
color:'#fff',
cursor:'pointer',
'font-size':'0.8em'
}).on('click',handler);
}
btnGroup.append(
makeBtn('GSR','#6c757d',function(){requestDeletion(page);}),
makeBtn('GSR+Tag','#495057',function(){requestDeletion(page);addDeleteTag(page,true);}),
makeBtn('Tag','#0d6efd',function(){addDeleteTag(page,false);}),
makeBtn('Delete','#dc3545',function(){deletePage(page);}),
makeBtn('Change','#198754',function(){changeRedirectTarget(page);})
);
entry.append(title,btnGroup);
listContainer.append(entry);
});
});
searchBar.on('input',function(){
var val=$(this).val().toLowerCase();
listContainer.children().each(function(){
var txt=$(this).text().toLowerCase();
$(this).toggle(txt.indexOf(val)!==-1);
});
});
menu.append(header,searchBar,listContainer);
$('body').append(overlay).append(menu);
});
}
function requestDeletion(page){
var lang=mw.config.get('wgContentLanguage');
var dbname=mw.config.get('wgDBname');
var prefix='';
if(dbname.includes('wikibooks'))prefix='b:'+lang+':';
else if(dbname.includes('wikiquote'))prefix='q:'+lang+':';
else if(dbname.includes('wikiversity'))prefix='wikiversity:'+lang+':';
else if(dbname.includes('wikisource'))prefix='wikisource:'+lang+':';
else if(dbname.includes('wikivoyage'))prefix='voy:'+lang+':';
else if(dbname.includes('wikispecies'))prefix='wikispecies:';
else if(dbname.includes('wiktionary'))prefix='wikt:'+lang+':';
else if(dbname.includes('wikinews'))prefix='n:'+lang+':';
else if(dbname.includes('commonswiki'))prefix='commons:';
else if(dbname.includes('metawiki'))prefix='m:';
else if(dbname.includes('mediawiki'))prefix='mw:';
else prefix=lang+':';
var formatted='* [[:'+prefix+page+']]: Broken redirect. <small>[[:m:User:PieWriter/BR.js|BR]]</small> ~~~~';
metaApi.postWithToken('csrf',{
action:'edit',
title:'Global sysops/Requests',
summary:'Requesting speedy deletion of broken redirect [[User:PieWriter/BR.js|using tool]]',
appendtext:'\n'+formatted
}).done(function(){alert('Deletion request added for '+page+' on Meta-Wiki');});
}
function addDeleteTag(page,gsr){
var tagText=gsr?'{{delete|Broken redirect|delete gsr}}':'{{delete|Broken redirect}}';
localApi.postWithToken('csrf',{
action:'edit',
title:page,
summary:'Tagging broken redirect for deletion ([[:m:User:PieWriter/BR.js|BR]])',
prependtext:tagText+'\n'
}).done(function(){alert('Added deletion tag to '+page+(gsr?' (with gsr)':''));});
}
function deletePage(page){
localApi.postWithToken('csrf',{
action:'delete',
title:page,
reason:'Broken redirect ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){alert('Page '+page+' deleted');})
.fail(function(){alert('Failed to delete '+page+'. You may need admin rights.');});
}
function changeRedirectTarget(page){
var newTarget=prompt('Enter new redirect target for '+page+':');
if(!newTarget)return;
localApi.get({
action:'query',
titles:newTarget
}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){
if(!confirm('Target page does not exist. Continue anyway?'))return;
}
localApi.get({
action:'query',
prop:'revisions',
titles:page,
rvprop:'content',
rvslots:'main',
formatversion:2
}).done(function(data){
var content=data.query.pages[0].revisions[0].slots.main.content;
var newContent;
if(/^#redirect\s*\[\[.*?\]\]/i.test(content)){
newContent=content.replace(/^#redirect\s*\[\[.*?\]\]/i,'#REDIRECT [['+newTarget+']]');
}else{
newContent='#REDIRECT [['+newTarget+']]\n'+content;
}
localApi.postWithToken('csrf',{
action:'edit',
title:page,
text:newContent,
summary:'Changing redirect target ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){
alert('Redirect target for '+page+' changed to '+newTarget);
}).fail(function(){
alert('Failed to change redirect target for '+page);
});
});
});
}
addButton();
});
// </nowiki>
5lavg4y5yzl74y9s6x2j9b637l3660h
737321
737304
2026-04-09T09:06:26Z
PieWriter
72123
Copied content from [[User:PieAlt/test.js]]
737321
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui'],function(){
var localApi=new mw.Api();
var metaApi=new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');
function addButton(){
mw.util.addPortletLink('p-tb','#','Find redirects','t-findredirects','List broken redirects');
$('#t-findredirects').on('click',function(e){
e.preventDefault();
showBrokenRedirects();
});
}
function getAdminCount(callback){
localApi.get({
action:'query',
list:'allusers',
augroup:'sysop',
aulimit:'max'
}).done(function(data){
var count=data.query.allusers.length;
callback(count);
});
}
function showBrokenRedirects(){
localApi.get({
action:'query',
list:'querypage',
qppage:'BrokenRedirects',
qplimit:'max'
}).done(function(data){
var redirects=data.query.querypage.results;
var overlay=$('<div>').css({
position:'fixed',top:0,left:0,width:'100%',height:'100%',
background:'rgba(0,0,0,0.6)','z-index':9998
});
var menu=$('<div>').css({
position:'fixed',top:'50%',left:'50%',
transform:'translate(-50%,-50%)',
background:'#fff',
padding:'0',
'z-index':9999,
width:'720px',
'max-height':'85%',
display:'flex',
'flex-direction':'column',
'border-radius':'12px',
overflow:'hidden',
'box-shadow':'0 10px 28px rgba(0,0,0,0.45)'
});
var header=$('<div>').css({
padding:'12px 16px',
background:'#2a4b8d',
color:'#fff',
display:'flex',
'justify-content':'space-between',
'align-items':'center'
});
var headerLeft=$('<div>').css({
display:'flex',
'flex-direction':'column'
});
var titleText=$('<span>').text('Broken Redirects Manager').css({
'font-weight':'bold',
'font-size':'1.05em'
});
var adminCountText=$('<span>').text('Loading admins...').css({
'font-size':'0.8em',
opacity:'0.85'
});
headerLeft.append(titleText,adminCountText);
getAdminCount(function(count){
adminCountText.text('Active administrators: '+count);
});
var closeBtn=$('<button>').text('✖').css({
border:'none',background:'transparent',color:'#fff','font-size':'1.2em',cursor:'pointer'
}).on('click',function(){overlay.remove();menu.remove();});
header.append(headerLeft,closeBtn);
var searchBar=$('<input>').attr('placeholder','Search redirects...').css({
width:'calc(100% - 20px)',
margin:'10px',
padding:'6px',
border:'1px solid #ccc',
'border-radius':'6px'
});
var listContainer=$('<div>').css({
overflow:'auto',
flex:'1',
padding:'10px'
});
redirects.forEach(function(item){
var page=item.title;
var ns=item.ns;
if(ns===2||ns===3||ns===118){return;}
localApi.get({action:'query',titles:page,prop:'info'}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){return;}
var entry=$('<div>').css({
display:'flex',
'justify-content':'space-between',
'align-items':'center',
padding:'8px',
margin:'4px 0',
background:'#f8f9fa',
'border-radius':'6px',
'transition':'0.2s'
}).hover(function(){
$(this).css('background','#e9ecef');
},function(){
$(this).css('background','#f8f9fa');
});
var title=$('<span>').text(page).css({
'flex':'1',
'font-size':'0.95em'
});
var btnGroup=$('<div>').css({
display:'flex',
gap:'4px'
});
function makeBtn(label,color,handler){
return $('<button>').text(label).css({
padding:'4px 8px',
border:'none',
'border-radius':'4px',
background:color,
color:'#fff',
cursor:'pointer',
'font-size':'0.8em'
}).on('click',handler);
}
btnGroup.append(
makeBtn('GSR','#6c757d',function(){requestDeletion(page);}),
makeBtn('GSR+Tag','#495057',function(){requestDeletion(page);addDeleteTag(page,true);}),
makeBtn('Tag','#0d6efd',function(){addDeleteTag(page,false);}),
makeBtn('Delete','#dc3545',function(){deletePage(page);}),
makeBtn('Change','#198754',function(){changeRedirectTarget(page);})
);
entry.append(title,btnGroup);
listContainer.append(entry);
});
});
searchBar.on('input',function(){
var val=$(this).val().toLowerCase();
listContainer.children().each(function(){
var txt=$(this).text().toLowerCase();
$(this).toggle(txt.indexOf(val)!==-1);
});
});
menu.append(header,searchBar,listContainer);
$('body').append(overlay).append(menu);
});
}
function requestDeletion(page){
var lang=mw.config.get('wgContentLanguage');
var dbname=mw.config.get('wgDBname');
var prefix='';
if(dbname.includes('wikibooks'))prefix='b:'+lang+':';
else if(dbname.includes('wikiquote'))prefix='q:'+lang+':';
else if(dbname.includes('wikiversity'))prefix='wikiversity:'+lang+':';
else if(dbname.includes('wikisource'))prefix='wikisource:'+lang+':';
else if(dbname.includes('wikivoyage'))prefix='voy:'+lang+':';
else if(dbname.includes('wikispecies'))prefix='wikispecies:';
else if(dbname.includes('wiktionary'))prefix='wikt:'+lang+':';
else if(dbname.includes('wikinews'))prefix='n:'+lang+':';
else if(dbname.includes('commonswiki'))prefix='commons:';
else if(dbname.includes('metawiki'))prefix='m:';
else if(dbname.includes('mediawiki'))prefix='mw:';
else prefix=lang+':';
var formatted='* [[:'+prefix+page+']]: Broken redirect. <small>[[:m:User:PieWriter/BR.js|BR]]</small> ~~~~';
metaApi.postWithToken('csrf',{
action:'edit',
title:'Global sysops/Requests',
summary:'Requesting speedy deletion of broken redirect [[User:PieWriter/BR.js|using tool]]',
appendtext:'\n'+formatted
}).done(function(){alert('Deletion request added for '+page+' on Meta-Wiki');});
}
function addDeleteTag(page,gsr){
var tagText=gsr?'{{delete|Broken redirect|delete gsr}}':'{{delete|Broken redirect}}';
localApi.postWithToken('csrf',{
action:'edit',
title:page,
summary:'Tagging broken redirect ([[:m:User:PieWriter/BR.js|BR]])',
prependtext:tagText+'\n'
}).done(function(){alert('Added deletion tag to '+page+(gsr?' (with gsr)':''));});
}
function deletePage(page){
localApi.postWithToken('csrf',{
action:'delete',
title:page,
reason:'Broken redirect ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){alert('Page '+page+' deleted');})
.fail(function(){alert('Failed to delete '+page+'. You may need admin rights.');});
}
function changeRedirectTarget(page){
var newTarget=prompt('Enter new redirect target for '+page+':');
if(!newTarget)return;
localApi.get({
action:'query',
titles:newTarget
}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){
if(!confirm('Target page does not exist. Continue anyway?'))return;
}
localApi.get({
action:'query',
prop:'revisions',
titles:page,
rvprop:'content',
rvslots:'main',
formatversion:2
}).done(function(data){
var content=data.query.pages[0].revisions[0].slots.main.content;
var newContent;
if(/^#redirect\s*\[\[.*?\]\]/i.test(content)){
newContent=content.replace(/^#redirect\s*\[\[.*?\]\]/i,'#REDIRECT [['+newTarget+']]');
}else{
newContent='#REDIRECT [['+newTarget+']]\n'+content;
}
localApi.postWithToken('csrf',{
action:'edit',
title:page,
text:newContent,
summary:'Changing redirect target ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){
alert('Redirect target for '+page+' changed to '+newTarget);
}).fail(function(){
alert('Failed to change redirect target for '+page);
});
});
});
}
addButton();
});
// </nowiki>
pc5pn92ol6dzm7jsz6dl2mz1lyaniuv
737322
737321
2026-04-09T09:07:33Z
PieWriter
72123
minor edits ([[m:Special:MyLanguage/User:Jon Harald Søby/diffedit|diffedit]])
737322
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui'],function(){
var localApi=new mw.Api();
var metaApi=new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');
function addButton(){
mw.util.addPortletLink('p-tb','#','Find redirects','t-findredirects','List broken redirects');
$('#t-findredirects').on('click',function(e){
e.preventDefault();
showBrokenRedirects();
});
}
function getAdminCount(callback){
localApi.get({
action:'query',
list:'allusers',
augroup:'sysop',
aulimit:'max'
}).done(function(data){
var count=data.query.allusers.length;
callback(count);
});
}
function showBrokenRedirects(){
localApi.get({
action:'query',
list:'querypage',
qppage:'BrokenRedirects',
qplimit:'max'
}).done(function(data){
var redirects=data.query.querypage.results;
var overlay=$('<div>').css({
position:'fixed',top:0,left:0,width:'100%',height:'100%',
background:'rgba(0,0,0,0.6)','z-index':9998
});
var menu=$('<div>').css({
position:'fixed',top:'50%',left:'50%',
transform:'translate(-50%,-50%)',
background:'#fff',
padding:'0',
'z-index':9999,
width:'720px',
'max-height':'85%',
display:'flex',
'flex-direction':'column',
'border-radius':'12px',
overflow:'hidden',
'box-shadow':'0 10px 28px rgba(0,0,0,0.45)'
});
var header=$('<div>').css({
padding:'12px 16px',
background:'#2a4b8d',
color:'#fff',
display:'flex',
'justify-content':'space-between',
'align-items':'center'
});
var headerLeft=$('<div>').css({
display:'flex',
'flex-direction':'column'
});
var titleText=$('<span>').text('Broken Redirects Manager').css({
'font-weight':'bold',
'font-size':'1.05em'
});
var adminCountText=$('<span>').text('Loading admins...').css({
'font-size':'0.8em',
opacity:'0.85'
});
headerLeft.append(titleText,adminCountText);
getAdminCount(function(count){
adminCountText.text('Active administrators: '+count);
});
var closeBtn=$('<button>').text('✖').css({
border:'none',background:'transparent',color:'#fff','font-size':'1.2em',cursor:'pointer'
}).on('click',function(){overlay.remove();menu.remove();});
header.append(headerLeft,closeBtn);
var searchBar=$('<input>').attr('placeholder','Search for redirects...').css({
width:'calc(100% - 20px)',
margin:'10px',
padding:'6px',
border:'1px solid #ccc',
'border-radius':'6px'
});
var listContainer=$('<div>').css({
overflow:'auto',
flex:'1',
padding:'10px'
});
redirects.forEach(function(item){
var page=item.title;
var ns=item.ns;
if(ns===2||ns===3||ns===118){return;}
localApi.get({action:'query',titles:page,prop:'info'}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){return;}
var entry=$('<div>').css({
display:'flex',
'justify-content':'space-between',
'align-items':'center',
padding:'8px',
margin:'4px 0',
background:'#f8f9fa',
'border-radius':'6px',
'transition':'0.2s'
}).hover(function(){
$(this).css('background','#e9ecef');
},function(){
$(this).css('background','#f8f9fa');
});
var title=$('<span>').text(page).css({
'flex':'1',
'font-size':'0.95em'
});
var btnGroup=$('<div>').css({
display:'flex',
gap:'4px'
});
function makeBtn(label,color,handler){
return $('<button>').text(label).css({
padding:'4px 8px',
border:'none',
'border-radius':'4px',
background:color,
color:'#fff',
cursor:'pointer',
'font-size':'0.8em'
}).on('click',handler);
}
btnGroup.append(
makeBtn('GSR','#6c757d',function(){requestDeletion(page);}),
makeBtn('GSR+Tag','#495057',function(){requestDeletion(page);addDeleteTag(page,true);}),
makeBtn('Tag','#0d6efd',function(){addDeleteTag(page,false);}),
makeBtn('Delete','#dc3545',function(){deletePage(page);}),
makeBtn('Change','#198754',function(){changeRedirectTarget(page);})
);
entry.append(title,btnGroup);
listContainer.append(entry);
});
});
searchBar.on('input',function(){
var val=$(this).val().toLowerCase();
listContainer.children().each(function(){
var txt=$(this).text().toLowerCase();
$(this).toggle(txt.indexOf(val)!==-1);
});
});
menu.append(header,searchBar,listContainer);
$('body').append(overlay).append(menu);
});
}
function requestDeletion(page){
var lang=mw.config.get('wgContentLanguage');
var dbname=mw.config.get('wgDBname');
var prefix='';
if(dbname.includes('wikibooks'))prefix='b:'+lang+':';
else if(dbname.includes('wikiquote'))prefix='q:'+lang+':';
else if(dbname.includes('wikiversity'))prefix='wikiversity:'+lang+':';
else if(dbname.includes('wikisource'))prefix='wikisource:'+lang+':';
else if(dbname.includes('wikivoyage'))prefix='voy:'+lang+':';
else if(dbname.includes('wikispecies'))prefix='wikispecies:';
else if(dbname.includes('wiktionary'))prefix='wikt:'+lang+':';
else if(dbname.includes('wikinews'))prefix='n:'+lang+':';
else if(dbname.includes('commonswiki'))prefix='commons:';
else if(dbname.includes('metawiki'))prefix='m:';
else if(dbname.includes('mediawiki'))prefix='mw:';
else prefix=lang+':';
var formatted='* [[:'+prefix+page+']]: Broken redirect. <small>[[:m:User:PieWriter/BR.js|BR]]</small> ~~~~';
metaApi.postWithToken('csrf',{
action:'edit',
title:'Global sysops/Requests',
summary:'Requesting speedy deletion of broken redirect [[User:PieWriter/BR.js|using tool]]',
appendtext:'\n'+formatted
}).done(function(){alert('Deletion request added for '+page+' on Meta-Wiki');});
}
function addDeleteTag(page,gsr){
var tagText=gsr?'{{delete|Broken redirect|delete gsr}}':'{{delete|Broken redirect}}';
localApi.postWithToken('csrf',{
action:'edit',
title:page,
summary:'Tagging broken redirect for deletion([[:m:User:PieWriter/BR.js|BR]])',
prependtext:tagText+'\n'
}).done(function(){alert('Added deletion tag to '+page+(gsr?' (with gsr)':''));});
}
function deletePage(page){
localApi.postWithToken('csrf',{
action:'delete',
title:page,
reason:'Broken redirect ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){alert('Page '+page+' deleted');})
.fail(function(){alert('Failed to delete '+page+'. You may need admin rights.');});
}
function changeRedirectTarget(page){
var newTarget=prompt('Enter new redirect target for '+page+':');
if(!newTarget)return;
localApi.get({
action:'query',
titles:newTarget
}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){
if(!confirm('Target page does not exist. Continue anyway?'))return;
}
localApi.get({
action:'query',
prop:'revisions',
titles:page,
rvprop:'content',
rvslots:'main',
formatversion:2
}).done(function(data){
var content=data.query.pages[0].revisions[0].slots.main.content;
var newContent;
if(/^#redirect\s*\[\[.*?\]\]/i.test(content)){
newContent=content.replace(/^#redirect\s*\[\[.*?\]\]/i,'#REDIRECT [['+newTarget+']]');
}else{
newContent='#REDIRECT [['+newTarget+']]\n'+content;
}
localApi.postWithToken('csrf',{
action:'edit',
title:page,
text:newContent,
summary:'Changing redirect target ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){
alert('Redirect target for '+page+' changed to '+newTarget);
}).fail(function(){
alert('Failed to change redirect target for '+page);
});
});
});
}
addButton();
});
// </nowiki>
21j7pacyyzvnw5i3rusf5mw2t53dctd
737324
737322
2026-04-09T09:13:27Z
PieWriter
72123
737324
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.util','mediawiki.api','oojs-ui'],function(){
var localApi=new mw.Api();
var metaApi=new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');
function addButton(){
mw.util.addPortletLink('p-tb','#','Find redirects','t-findredirects','List broken redirects');
$('#t-findredirects').on('click',function(e){
e.preventDefault();
showBrokenRedirects();
});
}
function getAdminCount(callback){
localApi.get({
action:'query',
list:'allusers',
augroup:'sysop',
aulimit:'max'
}).done(function(data){
var count=data.query.allusers.length;
callback(count);
});
}
function showBrokenRedirects(){
localApi.get({
action:'query',
list:'querypage',
qppage:'BrokenRedirects',
qplimit:'max'
}).done(function(data){
var redirects=data.query.querypage.results;
var overlay=$('<div>').css({
position:'fixed',top:0,left:0,width:'100%',height:'100%',
background:'rgba(0,0,0,0.6)','z-index':9998
});
var menu=$('<div>').css({
position:'fixed',top:'50%',left:'50%',
transform:'translate(-50%,-50%)',
background:'#fff',
padding:'0',
'z-index':9999,
width:'720px',
'max-height':'85%',
display:'flex',
'flex-direction':'column',
'border-radius':'12px',
overflow:'hidden',
'box-shadow':'0 10px 28px rgba(0,0,0,0.45)'
});
var header=$('<div>').css({
padding:'12px 16px',
background:'#2a4b8d',
color:'#fff',
display:'flex',
'justify-content':'space-between',
'align-items':'center'
});
var headerLeft=$('<div>').css({
display:'flex',
'flex-direction':'column'
});
var titleText=$('<span>').text('Broken Redirects Manager').css({
'font-weight':'bold',
'font-size':'1.05em'
});
var adminCountText=$('<span>').text('Loading admins...').css({
'font-size':'0.8em',
opacity:'0.85'
});
headerLeft.append(titleText,adminCountText);
getAdminCount(function(count){
adminCountText.text('Local administrators: '+count);
});
var closeBtn=$('<button>').text('✖').css({
border:'none',background:'transparent',color:'#fff','font-size':'1.2em',cursor:'pointer'
}).on('click',function(){overlay.remove();menu.remove();});
header.append(headerLeft,closeBtn);
var searchBar=$('<input>').attr('placeholder','Search for redirects...').css({
width:'calc(100% - 20px)',
margin:'10px',
padding:'6px',
border:'1px solid #ccc',
'border-radius':'6px'
});
var listContainer=$('<div>').css({
overflow:'auto',
flex:'1',
padding:'10px'
});
redirects.forEach(function(item){
var page=item.title;
var ns=item.ns;
if(ns===2||ns===3||ns===118){return;}
localApi.get({action:'query',titles:page,prop:'info'}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){return;}
var entry=$('<div>').css({
display:'flex',
'justify-content':'space-between',
'align-items':'center',
padding:'8px',
margin:'4px 0',
background:'#f8f9fa',
'border-radius':'6px',
'transition':'0.2s'
}).hover(function(){
$(this).css('background','#e9ecef');
},function(){
$(this).css('background','#f8f9fa');
});
var title=$('<span>').text(page).css({
'flex':'1',
'font-size':'0.95em'
});
var btnGroup=$('<div>').css({
display:'flex',
gap:'4px'
});
function makeBtn(label,color,handler){
return $('<button>').text(label).css({
padding:'4px 8px',
border:'none',
'border-radius':'4px',
background:color,
color:'#fff',
cursor:'pointer',
'font-size':'0.8em'
}).on('click',handler);
}
btnGroup.append(
makeBtn('GSR','#6c757d',function(){requestDeletion(page);}),
makeBtn('GSR+Tag','#495057',function(){requestDeletion(page);addDeleteTag(page,true);}),
makeBtn('Tag','#0d6efd',function(){addDeleteTag(page,false);}),
makeBtn('Delete','#dc3545',function(){deletePage(page);}),
makeBtn('Change','#198754',function(){changeRedirectTarget(page);})
);
entry.append(title,btnGroup);
listContainer.append(entry);
});
});
searchBar.on('input',function(){
var val=$(this).val().toLowerCase();
listContainer.children().each(function(){
var txt=$(this).text().toLowerCase();
$(this).toggle(txt.indexOf(val)!==-1);
});
});
menu.append(header,searchBar,listContainer);
$('body').append(overlay).append(menu);
});
}
function requestDeletion(page){
var lang=mw.config.get('wgContentLanguage');
var dbname=mw.config.get('wgDBname');
var prefix='';
if(dbname.includes('wikibooks'))prefix='b:'+lang+':';
else if(dbname.includes('wikiquote'))prefix='q:'+lang+':';
else if(dbname.includes('wikiversity'))prefix='wikiversity:'+lang+':';
else if(dbname.includes('wikisource'))prefix='wikisource:'+lang+':';
else if(dbname.includes('wikivoyage'))prefix='voy:'+lang+':';
else if(dbname.includes('wikispecies'))prefix='wikispecies:';
else if(dbname.includes('wiktionary'))prefix='wikt:'+lang+':';
else if(dbname.includes('wikinews'))prefix='n:'+lang+':';
else if(dbname.includes('commonswiki'))prefix='commons:';
else if(dbname.includes('metawiki'))prefix='m:';
else if(dbname.includes('mediawiki'))prefix='mw:';
else prefix=lang+':';
var formatted='* [[:'+prefix+page+']]: Broken redirect. <small>[[:m:User:PieWriter/BR.js|BR]]</small> ~~~~';
metaApi.postWithToken('csrf',{
action:'edit',
title:'Global sysops/Requests',
summary:'Requesting speedy deletion of broken redirect [[User:PieWriter/BR.js|using tool]]',
appendtext:'\n'+formatted
}).done(function(){alert('Deletion request added for '+page+' on Meta-Wiki');});
}
function addDeleteTag(page,gsr){
var tagText=gsr?'{{delete|Broken redirect|delete gsr}}':'{{delete|Broken redirect}}';
localApi.postWithToken('csrf',{
action:'edit',
title:page,
summary:'Tagging broken redirect for deletion([[:m:User:PieWriter/BR.js|BR]])',
prependtext:tagText+'\n'
}).done(function(){alert('Added deletion tag to '+page+(gsr?' (with gsr)':''));});
}
function deletePage(page){
localApi.postWithToken('csrf',{
action:'delete',
title:page,
reason:'Broken redirect ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){alert('Page '+page+' deleted');})
.fail(function(){alert('Failed to delete '+page+'. You may need admin rights.');});
}
function changeRedirectTarget(page){
var newTarget=prompt('Enter new redirect target for '+page+':');
if(!newTarget)return;
localApi.get({
action:'query',
titles:newTarget
}).done(function(check){
var pages=check.query.pages;
var exists=true;
$.each(pages,function(id,p){if(p.missing!==undefined){exists=false;}});
if(!exists){
if(!confirm('Target page does not exist. Continue anyway?'))return;
}
localApi.get({
action:'query',
prop:'revisions',
titles:page,
rvprop:'content',
rvslots:'main',
formatversion:2
}).done(function(data){
var content=data.query.pages[0].revisions[0].slots.main.content;
var newContent;
if(/^#redirect\s*\[\[.*?\]\]/i.test(content)){
newContent=content.replace(/^#redirect\s*\[\[.*?\]\]/i,'#REDIRECT [['+newTarget+']]');
}else{
newContent='#REDIRECT [['+newTarget+']]\n'+content;
}
localApi.postWithToken('csrf',{
action:'edit',
title:page,
text:newContent,
summary:'Changing redirect target ([[:m:User:PieWriter/BR.js|BR]])'
}).done(function(){
alert('Redirect target for '+page+' changed to '+newTarget);
}).fail(function(){
alert('Failed to change redirect target for '+page);
});
});
});
}
addButton();
});
// </nowiki>
6ekr5y5r6og3ki9ms3d3e4024ctll0j
User:PieAlt/test3.js
2
174758
737316
2026-04-09T09:02:12Z
PieAlt
73198
Been working on this
737316
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.api','mediawiki.util','oojs-ui'], function () {
var api = new mw.Api();
function getPageContent(title) {
return api.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: title,
formatversion: 2
}).then(function (data) {
var page = data.query.pages[0];
return page.revisions ? page.revisions[0].content : '';
});
}
function editPage(title, content, summary) {
return api.postWithToken('csrf', {
action: 'edit',
title: title,
text: content,
summary: summary
});
}
function openDialog() {
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
function Dialog(config) {
Dialog.super.call(this, config);
}
OO.inheritClass(Dialog, OO.ui.ProcessDialog);
Dialog.static.name = 'copyPageDialog';
Dialog.static.title = 'Copy page content';
Dialog.static.actions = [
{ action: 'save', label: 'Copy', flags: ['primary', 'progressive'] },
{ label: 'Cancel', flags: 'safe' }
];
Dialog.prototype.initialize = function () {
Dialog.super.prototype.initialize.apply(this, arguments);
this.targetInput = new OO.ui.TextInputWidget({
placeholder: 'Target page title'
});
this.summaryInput = new OO.ui.TextInputWidget({
placeholder: 'Edit summary'
});
this.panel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.panel.$element.append(
$('<p>').text('Target page:'),
this.targetInput.$element,
$('<p>').text('Edit summary:'),
this.summaryInput.$element
);
this.$body.append(this.panel.$element);
};
Dialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === 'save') {
return new OO.ui.Process(function () {
var target = dialog.targetInput.getValue();
var summary = dialog.summaryInput.getValue() || 'Copied content from [[' + mw.config.get('wgPageName') + ']]';
if (!target) {
alert('Target page required');
return;
}
return getPageContent(mw.config.get('wgPageName')).then(function (content) {
return editPage(target, content, summary);
}).then(function () {
location.reload();
});
});
}
return Dialog.super.prototype.getActionProcess.call(this, action);
};
var dialog = new Dialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
}
function addButton() {
mw.util.addPortletLink(
'p-cactions',
'#',
'Copy page',
'ca-copy-page'
).addEventListener('click', function (e) {
e.preventDefault();
openDialog();
});
}
$(addButton);
});
// </nowiki>
hktgh3evwx41w5l9iukndzbwyttjldp
User:PieAlt/sandbox12
2
174759
737317
2026-04-09T09:02:34Z
PieAlt
73198
test
737317
wikitext
text/x-wiki
// <nowiki>
mw.loader.using(['mediawiki.api','mediawiki.util','oojs-ui'], function () {
var api = new mw.Api();
function getPageContent(title) {
return api.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: title,
formatversion: 2
}).then(function (data) {
var page = data.query.pages[0];
return page.revisions ? page.revisions[0].content : '';
});
}
function editPage(title, content, summary) {
return api.postWithToken('csrf', {
action: 'edit',
title: title,
text: content,
summary: summary
});
}
function openDialog() {
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
function Dialog(config) {
Dialog.super.call(this, config);
}
OO.inheritClass(Dialog, OO.ui.ProcessDialog);
Dialog.static.name = 'copyPageDialog';
Dialog.static.title = 'Copy page content';
Dialog.static.actions = [
{ action: 'save', label: 'Copy', flags: ['primary', 'progressive'] },
{ label: 'Cancel', flags: 'safe' }
];
Dialog.prototype.initialize = function () {
Dialog.super.prototype.initialize.apply(this, arguments);
this.targetInput = new OO.ui.TextInputWidget({
placeholder: 'Target page title'
});
this.summaryInput = new OO.ui.TextInputWidget({
placeholder: 'Edit summary'
});
this.panel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.panel.$element.append(
$('<p>').text('Target page:'),
this.targetInput.$element,
$('<p>').text('Edit summary:'),
this.summaryInput.$element
);
this.$body.append(this.panel.$element);
};
Dialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === 'save') {
return new OO.ui.Process(function () {
var target = dialog.targetInput.getValue();
var summary = dialog.summaryInput.getValue() || 'Copied content from [[' + mw.config.get('wgPageName') + ']]';
if (!target) {
alert('Target page required');
return;
}
return getPageContent(mw.config.get('wgPageName')).then(function (content) {
return editPage(target, content, summary);
}).then(function () {
location.reload();
});
});
}
return Dialog.super.prototype.getActionProcess.call(this, action);
};
var dialog = new Dialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
}
function addButton() {
mw.util.addPortletLink(
'p-cactions',
'#',
'Copy page',
'ca-copy-page'
).addEventListener('click', function (e) {
e.preventDefault();
openDialog();
});
}
$(addButton);
});
// </nowiki>
hktgh3evwx41w5l9iukndzbwyttjldp
User:PieWriter/copier.js
2
174761
737320
2026-04-09T09:04:46Z
PieWriter
72123
Copied content from [[User:PieAlt/test3.js]]
737320
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.api','mediawiki.util','oojs-ui'], function () {
var api = new mw.Api();
function getPageContent(title) {
return api.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: title,
formatversion: 2
}).then(function (data) {
var page = data.query.pages[0];
return page.revisions ? page.revisions[0].content : '';
});
}
function editPage(title, content, summary) {
return api.postWithToken('csrf', {
action: 'edit',
title: title,
text: content,
summary: summary
});
}
function openDialog() {
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
function Dialog(config) {
Dialog.super.call(this, config);
}
OO.inheritClass(Dialog, OO.ui.ProcessDialog);
Dialog.static.name = 'copyPageDialog';
Dialog.static.title = 'Copy page content';
Dialog.static.actions = [
{ action: 'save', label: 'Copy', flags: ['primary', 'progressive'] },
{ label: 'Cancel', flags: 'safe' }
];
Dialog.prototype.initialize = function () {
Dialog.super.prototype.initialize.apply(this, arguments);
this.targetInput = new OO.ui.TextInputWidget({
placeholder: 'Target page title'
});
this.summaryInput = new OO.ui.TextInputWidget({
placeholder: 'Edit summary'
});
this.panel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.panel.$element.append(
$('<p>').text('Target page:'),
this.targetInput.$element,
$('<p>').text('Edit summary:'),
this.summaryInput.$element
);
this.$body.append(this.panel.$element);
};
Dialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === 'save') {
return new OO.ui.Process(function () {
var target = dialog.targetInput.getValue();
var summary = dialog.summaryInput.getValue() || 'Copied content from [[' + mw.config.get('wgPageName') + ']]';
if (!target) {
alert('Target page required');
return;
}
return getPageContent(mw.config.get('wgPageName')).then(function (content) {
return editPage(target, content, summary);
}).then(function () {
location.reload();
});
});
}
return Dialog.super.prototype.getActionProcess.call(this, action);
};
var dialog = new Dialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
}
function addButton() {
mw.util.addPortletLink(
'p-cactions',
'#',
'Copy page',
'ca-copy-page'
).addEventListener('click', function (e) {
e.preventDefault();
openDialog();
});
}
$(addButton);
});
// </nowiki>
hktgh3evwx41w5l9iukndzbwyttjldp
737323
737320
2026-04-09T09:08:56Z
PieWriter
72123
737323
javascript
text/javascript
// <nowiki>
mw.loader.using(['mediawiki.api','mediawiki.util','oojs-ui'], function () {
var api = new mw.Api();
function getPageContent(title) {
return api.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: title,
formatversion: 2
}).then(function (data) {
var page = data.query.pages[0];
return page.revisions ? page.revisions[0].content : '';
});
}
function editPage(title, content, summary) {
return api.postWithToken('csrf', {
action: 'edit',
title: title,
text: content,
summary: summary
});
}
function openDialog() {
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
function Dialog(config) {
Dialog.super.call(this, config);
}
OO.inheritClass(Dialog, OO.ui.ProcessDialog);
Dialog.static.name = 'copyPageDialog';
Dialog.static.title = 'Copy page content';
Dialog.static.actions = [
{ action: 'save', label: 'Copy', flags: ['primary', 'progressive'] },
{ label: 'Cancel', flags: 'safe' }
];
Dialog.prototype.initialize = function () {
Dialog.super.prototype.initialize.apply(this, arguments);
this.targetInput = new OO.ui.TextInputWidget({
placeholder: 'Target page title'
});
this.summaryInput = new OO.ui.TextInputWidget({
placeholder: 'Edit summary'
});
this.panel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.panel.$element.append(
$('<p>').text('Target page:'),
this.targetInput.$element,
$('<p>').text('Edit summary:'),
this.summaryInput.$element
);
this.$body.append(this.panel.$element);
};
Dialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === 'save') {
return new OO.ui.Process(function () {
var target = dialog.targetInput.getValue();
var summary = dialog.summaryInput.getValue() || 'Copied content from [[' + mw.config.get('wgPageName') + ']] using [[:test:User:PieWriter/copier.js|script]]';
if (!target) {
alert('Target page required');
return;
}
return getPageContent(mw.config.get('wgPageName')).then(function (content) {
return editPage(target, content, summary);
}).then(function () {
location.reload();
});
});
}
return Dialog.super.prototype.getActionProcess.call(this, action);
};
var dialog = new Dialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
}
function addButton() {
mw.util.addPortletLink(
'p-cactions',
'#',
'Copy page',
'ca-copy-page'
).addEventListener('click', function (e) {
e.preventDefault();
openDialog();
});
}
$(addButton);
});
// </nowiki>
b7t8pgygxrwqtjv1qted885mvx8rehr